Compare commits
223 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d5020b607 | ||
|
|
7d8b7596ce | ||
|
|
08b5c9f25a | ||
|
|
82a4f8d57b | ||
|
|
538a79319e | ||
|
|
0555dcbc43 | ||
|
|
7b1f19ff9f | ||
|
|
9cac427eb9 | ||
|
|
1e5950028f | ||
|
|
7a7d343c3f | ||
|
|
ebf6388414 | ||
|
|
fa5426be53 | ||
|
|
4c60bb20c9 | ||
|
|
b02f3c8f8b | ||
|
|
d5f769e5a4 | ||
|
|
2afd1188eb | ||
|
|
1c6301242d | ||
|
|
06016727d9 | ||
|
|
1ee0c27ff9 | ||
|
|
7f03a630c6 | ||
|
|
2aa19f4f3b | ||
|
|
4be3c16adc | ||
|
|
f1f99a67dd | ||
|
|
a4f15ffe15 | ||
|
|
c11dad0cb3 | ||
|
|
2bf2469657 | ||
|
|
a23b02e962 | ||
|
|
905ee9bf27 | ||
|
|
e26869b30b | ||
|
|
90867792d9 | ||
|
|
1238802a75 | ||
|
|
8f7c50b88f | ||
|
|
df24434689 | ||
|
|
878748803d | ||
|
|
80bbf7f22f | ||
|
|
28b0ea565a | ||
|
|
07ccd1dea9 | ||
|
|
6a8fba535a | ||
|
|
f1b82ad0d3 | ||
|
|
0035bc09dd | ||
|
|
863c57d7fb | ||
|
|
00ccb68ce2 | ||
|
|
c2995dcd25 | ||
|
|
7b63366627 | ||
|
|
ababa6bf27 | ||
|
|
3f1f42d4f0 | ||
|
|
4d76bd6d36 | ||
|
|
ab34d1682c | ||
|
|
552b7a6356 | ||
|
|
f86acf673a | ||
|
|
c6f49bafca | ||
|
|
fdbb325611 | ||
|
|
8c557ced85 | ||
|
|
ad0f7de506 | ||
|
|
15ec9bb454 | ||
|
|
9fe7558cfe | ||
|
|
79633dbe1d | ||
|
|
f325306e20 | ||
|
|
4778656063 | ||
|
|
f7e00523c2 | ||
|
|
bf3df7230b | ||
|
|
c5182e0ed6 | ||
|
|
4081438cba | ||
|
|
b72fe2bf37 | ||
|
|
4d9c687709 | ||
|
|
3d4b970967 | ||
|
|
792f295c45 | ||
|
|
af28753558 | ||
|
|
24fb663599 | ||
|
|
140506c9c3 | ||
|
|
519f159c8c | ||
|
|
bb019cbcd9 | ||
|
|
84219627ff | ||
|
|
f218e878c6 | ||
|
|
520f47955b | ||
|
|
8110ee18f1 | ||
|
|
55bf817310 | ||
|
|
f2f55f8118 | ||
|
|
f48e7cd0d4 | ||
|
|
c82e0c8b9b | ||
|
|
c72877e16b | ||
|
|
ccc9076397 | ||
|
|
1c716ecf4a | ||
|
|
2cc471b944 | ||
|
|
a6c450109a | ||
|
|
e13707305b | ||
|
|
68bc0383f4 | ||
|
|
878f4f3687 | ||
|
|
48a721adda | ||
|
|
548e8da78c | ||
|
|
beb28214cf | ||
|
|
b4bb31491b | ||
|
|
0b5f007327 | ||
|
|
d31a4aeea3 | ||
|
|
97acd873eb | ||
|
|
5cf3538be3 | ||
|
|
a34921701d | ||
|
|
5fcf54de09 | ||
|
|
68372bf995 | ||
|
|
9f99ac1531 | ||
|
|
b7384fda6b | ||
|
|
8f4d610825 | ||
|
|
6bbf4b2575 | ||
|
|
32db884620 | ||
|
|
1b736b36f8 | ||
|
|
22dfe5eb04 | ||
|
|
8fd49e20d5 | ||
|
|
c791070126 | ||
|
|
807603fd4f | ||
|
|
a944e5e5ab | ||
|
|
33e916cac2 | ||
|
|
a99d71dc41 | ||
|
|
67d91f1a0d | ||
|
|
de2db7d24e | ||
|
|
b360f77ea0 | ||
|
|
e869528522 | ||
|
|
7c0317d232 | ||
|
|
0e49497da1 | ||
|
|
0380382c47 | ||
|
|
23201b8c86 | ||
|
|
5f357c9f84 | ||
|
|
244272621c | ||
|
|
5792b84880 | ||
|
|
8e644093f9 | ||
|
|
9421cb57ca | ||
|
|
bd7cbffe10 | ||
|
|
b293882f75 | ||
|
|
0984b2049b | ||
|
|
d4ec4989e7 | ||
|
|
230433f784 | ||
|
|
d4dda481be | ||
|
|
1d3b94f544 | ||
|
|
78434f0016 | ||
|
|
c75145de49 | ||
|
|
5e3480f9f3 | ||
|
|
f74fdc8ed4 | ||
|
|
9ba62f8bf8 | ||
|
|
f2b9dea2b6 | ||
|
|
f92aa22375 | ||
|
|
d65001f412 | ||
|
|
9ea223cd0a | ||
|
|
addf73bc27 | ||
|
|
3f853d0d5e | ||
|
|
4c46278be1 | ||
|
|
0edc51083c | ||
|
|
d906bec333 | ||
|
|
a47c4942b7 | ||
|
|
cd8df9e938 | ||
|
|
5c37f98274 | ||
|
|
f452436dbd | ||
|
|
9375bb103b | ||
|
|
3222579cb4 | ||
|
|
4348b50e54 | ||
|
|
3b7c58b6e8 | ||
|
|
664b89a5df | ||
|
|
628c22ad61 | ||
|
|
db62a041c4 | ||
|
|
ea3ffea797 | ||
|
|
c49c8d67a2 | ||
|
|
3b01df4c56 | ||
|
|
9221516bad | ||
|
|
1988cf54f5 | ||
|
|
8a8d8979a7 | ||
|
|
e983b39f8e | ||
|
|
b6c5cd4b46 | ||
|
|
210b27ab25 | ||
|
|
63e9e5ecec | ||
|
|
771ca15ea2 | ||
|
|
b6b403b5e2 | ||
|
|
9976c94481 | ||
|
|
b9dca8ceae | ||
|
|
03b9331a9a | ||
|
|
f629834d6a | ||
|
|
25952cce50 | ||
|
|
6eba83ccec | ||
|
|
3c1ce80528 | ||
|
|
b7851e87b2 | ||
|
|
d8e06fd968 | ||
|
|
c5a5de50a7 | ||
|
|
7715e7b4ab | ||
|
|
5a3ed3d3d4 | ||
|
|
c1e2394194 | ||
|
|
6abb569df1 | ||
|
|
e59e33bc3b | ||
|
|
a671dc1937 | ||
|
|
426b52aee4 | ||
|
|
87fe011565 | ||
|
|
30c03af5b0 | ||
|
|
c9c8fc8705 | ||
|
|
9cd8030808 | ||
|
|
5411d4152c | ||
|
|
c5a05afe1c | ||
|
|
5b44f2ae33 | ||
|
|
039b30da4f | ||
|
|
df45378bce | ||
|
|
3729d2c23f | ||
|
|
89ece413c8 | ||
|
|
c1b361397f | ||
|
|
ada0715343 | ||
|
|
5cdc687f6d | ||
|
|
48f0e16fde | ||
|
|
d6b6600ce9 | ||
|
|
82634e9e1b | ||
|
|
225e5e58bc | ||
|
|
21d135a8ab | ||
|
|
7a0636a48a | ||
|
|
0b9ca77281 | ||
|
|
feaf631b47 | ||
|
|
ec24c19821 | ||
|
|
36a120fa0f | ||
|
|
fb169d5054 | ||
|
|
59fa7b6a1d | ||
|
|
201d832b92 | ||
|
|
31b4ef5458 | ||
|
|
b06ad0cdaa | ||
|
|
f3914bc3b7 | ||
|
|
aea870974b | ||
|
|
7f6265ba05 | ||
|
|
85542cb278 | ||
|
|
f6e0873d83 | ||
|
|
745b262dfc | ||
|
|
73df755f70 | ||
|
|
762a1b98d3 |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,14 +1,6 @@
|
||||
.vs/
|
||||
bin/
|
||||
obj/
|
||||
wwwroot/images/
|
||||
cartracker.db
|
||||
data/cartracker.db
|
||||
wwwroot/documents/
|
||||
wwwroot/temp/
|
||||
wwwroot/imports/
|
||||
wwwroot/translations/
|
||||
config/userConfig.json
|
||||
data/
|
||||
CarCareTracker.csproj.user
|
||||
Properties/launchSettings.json
|
||||
data/cartracker-log.db
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,7 +55,7 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
}
|
||||
}
|
||||
var successResponse = new OperationResponse { Success = true, Message = "Token Generated!" };
|
||||
var successResponse = OperationResponse.Succeed("Token Generated!");
|
||||
return Json(successResponse);
|
||||
} else
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace CarCareTracker.Controllers
|
||||
var originalFileName = Path.GetFileNameWithoutExtension(file.FileName);
|
||||
if (originalFileName == "en_US")
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "The translation file name en_US is reserved." });
|
||||
return Json(OperationResponse.Failed("The translation file name en_US is reserved."));
|
||||
}
|
||||
var fileName = UploadFile(file);
|
||||
//move file from temp to translation folder.
|
||||
@@ -41,9 +41,9 @@ namespace CarCareTracker.Controllers
|
||||
if (!string.IsNullOrWhiteSpace(uploadedFilePath))
|
||||
{
|
||||
var result = _fileHelper.RenameFile(uploadedFilePath, originalFileName);
|
||||
return Json(new OperationResponse { Success = result, Message = string.Empty });
|
||||
return Json(OperationResponse.Conditional(result));
|
||||
}
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
return Json(OperationResponse.Failed());
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@@ -81,7 +81,7 @@ namespace CarCareTracker.Controllers
|
||||
private string UploadFile(IFormFile fileToUpload)
|
||||
{
|
||||
string uploadDirectory = "temp/";
|
||||
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
|
||||
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
|
||||
if (!Directory.Exists(uploadPath))
|
||||
Directory.CreateDirectory(uploadPath);
|
||||
string fileName = Guid.NewGuid() + Path.GetExtension(fileToUpload.FileName);
|
||||
@@ -95,7 +95,7 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult UploadCoordinates(List<string> coordinates)
|
||||
{
|
||||
string uploadDirectory = "temp/";
|
||||
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
|
||||
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
|
||||
if (!Directory.Exists(uploadPath))
|
||||
Directory.CreateDirectory(uploadPath);
|
||||
string fileName = Guid.NewGuid() + ".csv";
|
||||
|
||||
@@ -55,6 +55,58 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
return View(model: tab);
|
||||
}
|
||||
[Route("/kiosk")]
|
||||
public IActionResult Kiosk(string exclusions, KioskMode kioskMode = KioskMode.Vehicle)
|
||||
{
|
||||
try {
|
||||
var viewModel = new KioskViewModel
|
||||
{
|
||||
Exclusions = string.IsNullOrWhiteSpace(exclusions) ? new List<int>() : exclusions.Split(',').Select(x => int.Parse(x)).ToList(),
|
||||
KioskMode = kioskMode
|
||||
};
|
||||
return View(viewModel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return View(new KioskViewModel());
|
||||
}
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult KioskContent(KioskViewModel kioskParameters)
|
||||
{
|
||||
var vehiclesStored = _dataAccess.GetVehicles();
|
||||
if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||
}
|
||||
vehiclesStored.RemoveAll(x => kioskParameters.Exclusions.Contains(x.Id));
|
||||
var userConfig = _config.GetUserConfig(User);
|
||||
if (userConfig.HideSoldVehicles)
|
||||
{
|
||||
vehiclesStored.RemoveAll(x => !string.IsNullOrWhiteSpace(x.SoldDate));
|
||||
}
|
||||
switch (kioskParameters.KioskMode)
|
||||
{
|
||||
case KioskMode.Vehicle:
|
||||
{
|
||||
var kioskResult = _vehicleLogic.GetVehicleInfo(vehiclesStored);
|
||||
return PartialView("_Kiosk", kioskResult);
|
||||
}
|
||||
case KioskMode.Plan:
|
||||
{
|
||||
var kioskResult = _vehicleLogic.GetPlans(vehiclesStored, true);
|
||||
return PartialView("_KioskPlan", kioskResult);
|
||||
}
|
||||
case KioskMode.Reminder:
|
||||
{
|
||||
var kioskResult = _vehicleLogic.GetReminders(vehiclesStored, false);
|
||||
return PartialView("_KioskReminder", kioskResult);
|
||||
}
|
||||
}
|
||||
var result = _vehicleLogic.GetVehicleInfo(vehiclesStored);
|
||||
return PartialView("_Kiosk", result);
|
||||
}
|
||||
public IActionResult Garage()
|
||||
{
|
||||
var vehiclesStored = _dataAccess.GetVehicles();
|
||||
@@ -118,19 +170,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||
}
|
||||
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>();
|
||||
foreach (Vehicle vehicle in vehiclesStored)
|
||||
{
|
||||
var vehicleReminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
|
||||
vehicleReminders.RemoveAll(x => x.Metric == ReminderMetric.Odometer);
|
||||
//we don't care about mileages so we can basically fake the current vehicle mileage.
|
||||
if (vehicleReminders.Any())
|
||||
{
|
||||
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(vehicleReminders, 0, DateTime.Now);
|
||||
reminderUrgency = reminderUrgency.Select(x => new ReminderRecordViewModel { Id = x.Id, Date = x.Date, Urgency = x.Urgency, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" }).ToList();
|
||||
reminders.AddRange(reminderUrgency);
|
||||
}
|
||||
}
|
||||
var reminders = _vehicleLogic.GetReminders(vehiclesStored, true);
|
||||
return PartialView("_Calendar", reminders);
|
||||
}
|
||||
public IActionResult ViewCalendarReminder(int reminderId)
|
||||
@@ -184,10 +224,6 @@ namespace CarCareTracker.Controllers
|
||||
var result = _config.SaveUserConfig(User, existingConfig);
|
||||
return Json(result);
|
||||
}
|
||||
public IActionResult Privacy()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
public IActionResult GetExtraFieldsModal(int importMode = 0)
|
||||
{
|
||||
@@ -243,12 +279,12 @@ namespace CarCareTracker.Controllers
|
||||
var result = _loginLogic.UpdateUserDetails(userId, userAccount);
|
||||
return Json(result);
|
||||
}
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
return Json(OperationResponse.Failed());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
return Json(OperationResponse.Failed());
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
@@ -456,18 +492,69 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
if (translationsDownloaded > 0)
|
||||
{
|
||||
return Json(new OperationResponse() { Success = true, Message = $"{translationsDownloaded} Translations Downloaded" });
|
||||
return Json(OperationResponse.Succeed($"{translationsDownloaded} Translations Downloaded"));
|
||||
} else
|
||||
{
|
||||
return Json(new OperationResponse() { Success = false, Message = "No Translations Downloaded" });
|
||||
return Json(OperationResponse.Failed("No Translations Downloaded"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Unable to retrieve translations: {ex.Message}");
|
||||
return Json(new OperationResponse() { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
return Json(OperationResponse.Failed());
|
||||
}
|
||||
}
|
||||
public ActionResult GetVehicleSelector(int vehicleId)
|
||||
{
|
||||
var vehiclesStored = _dataAccess.GetVehicles();
|
||||
if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||
}
|
||||
if (vehicleId != default)
|
||||
{
|
||||
vehiclesStored.RemoveAll(x => x.Id == vehicleId);
|
||||
}
|
||||
var userConfig = _config.GetUserConfig(User);
|
||||
if (userConfig.HideSoldVehicles)
|
||||
{
|
||||
vehiclesStored.RemoveAll(x => !string.IsNullOrWhiteSpace(x.SoldDate));
|
||||
}
|
||||
return PartialView("_VehicleSelector", vehiclesStored);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpGet]
|
||||
public IActionResult GetCustomWidgetEditor()
|
||||
{
|
||||
if (_config.GetCustomWidgetsEnabled())
|
||||
{
|
||||
var customWidgetData = _fileHelper.GetWidgets();
|
||||
return PartialView("_WidgetEditor", customWidgetData);
|
||||
}
|
||||
return Json(string.Empty);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpPost]
|
||||
public IActionResult SaveCustomWidgets(string widgetsData)
|
||||
{
|
||||
if (_config.GetCustomWidgetsEnabled())
|
||||
{
|
||||
var saveResult = _fileHelper.SaveWidgets(widgetsData);
|
||||
return Json(saveResult);
|
||||
}
|
||||
return Json(false);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpPost]
|
||||
public IActionResult DeleteCustomWidgets()
|
||||
{
|
||||
if (_config.GetCustomWidgetsEnabled())
|
||||
{
|
||||
var deleteResult = _fileHelper.DeleteWidgets();
|
||||
return Json(deleteResult);
|
||||
}
|
||||
return Json(false);
|
||||
}
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
|
||||
@@ -240,6 +240,12 @@ namespace CarCareTracker.Controllers
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult SendRegistrationToken(LoginModel credentials)
|
||||
{
|
||||
var result = _loginLogic.SendRegistrationToken(credentials);
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult RequestResetPassword(LoginModel credentials)
|
||||
{
|
||||
var result = _loginLogic.RequestResetPassword(credentials);
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Npgsql;
|
||||
using System.IO.Compression;
|
||||
using JsonSerializer=System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace CarCareTracker.Controllers
|
||||
{
|
||||
@@ -14,7 +15,7 @@ namespace CarCareTracker.Controllers
|
||||
private IConfigHelper _configHelper;
|
||||
private IFileHelper _fileHelper;
|
||||
private readonly ILogger<MigrationController> _logger;
|
||||
public MigrationController(IConfigHelper configHelper, IFileHelper fileHelper, IConfiguration serverConfig, ILogger<MigrationController> logger)
|
||||
public MigrationController(IConfigHelper configHelper, IFileHelper fileHelper, ILogger<MigrationController> logger)
|
||||
{
|
||||
_configHelper = configHelper;
|
||||
_fileHelper = fileHelper;
|
||||
@@ -65,7 +66,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_configHelper.GetServerPostgresConnection()))
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "Postgres connection not set up" });
|
||||
return Json(OperationResponse.Failed("Postgres connection not set up"));
|
||||
}
|
||||
var tempFolder = $"temp/{Guid.NewGuid()}";
|
||||
var tempPath = $"{tempFolder}/cartracker.db";
|
||||
@@ -105,7 +106,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
Vehicle vehicle = System.Text.Json.JsonSerializer.Deserialize<Vehicle>(reader["data"] as string);
|
||||
Vehicle vehicle = JsonSerializer.Deserialize<Vehicle>(reader["data"] as string);
|
||||
vehicles.Add(vehicle);
|
||||
}
|
||||
}
|
||||
@@ -123,7 +124,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
repairrecords.Add(System.Text.Json.JsonSerializer.Deserialize<CollisionRecord>(reader["data"] as string));
|
||||
repairrecords.Add(JsonSerializer.Deserialize<CollisionRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in repairrecords)
|
||||
@@ -140,7 +141,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
upgraderecords.Add(System.Text.Json.JsonSerializer.Deserialize<UpgradeRecord>(reader["data"] as string));
|
||||
upgraderecords.Add(JsonSerializer.Deserialize<UpgradeRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in upgraderecords)
|
||||
@@ -157,7 +158,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
servicerecords.Add(System.Text.Json.JsonSerializer.Deserialize<ServiceRecord>(reader["data"] as string));
|
||||
servicerecords.Add(JsonSerializer.Deserialize<ServiceRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in servicerecords)
|
||||
@@ -176,7 +177,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
gasrecords.Add(System.Text.Json.JsonSerializer.Deserialize<GasRecord>(reader["data"] as string));
|
||||
gasrecords.Add(JsonSerializer.Deserialize<GasRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in gasrecords)
|
||||
@@ -193,7 +194,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
noterecords.Add(System.Text.Json.JsonSerializer.Deserialize<Note>(reader["data"] as string));
|
||||
noterecords.Add(JsonSerializer.Deserialize<Note>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in noterecords)
|
||||
@@ -210,7 +211,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
odometerrecords.Add(System.Text.Json.JsonSerializer.Deserialize<OdometerRecord>(reader["data"] as string));
|
||||
odometerrecords.Add(JsonSerializer.Deserialize<OdometerRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in odometerrecords)
|
||||
@@ -227,7 +228,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
reminderrecords.Add(System.Text.Json.JsonSerializer.Deserialize<ReminderRecord>(reader["data"] as string));
|
||||
reminderrecords.Add(JsonSerializer.Deserialize<ReminderRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in reminderrecords)
|
||||
@@ -246,7 +247,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
planrecords.Add(System.Text.Json.JsonSerializer.Deserialize<PlanRecord>(reader["data"] as string));
|
||||
planrecords.Add(JsonSerializer.Deserialize<PlanRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in planrecords)
|
||||
@@ -263,7 +264,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
planrecordtemplates.Add(System.Text.Json.JsonSerializer.Deserialize<PlanRecordInput>(reader["data"] as string));
|
||||
planrecordtemplates.Add(JsonSerializer.Deserialize<PlanRecordInput>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in planrecordtemplates)
|
||||
@@ -280,7 +281,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
supplyrecords.Add(System.Text.Json.JsonSerializer.Deserialize<SupplyRecord>(reader["data"] as string));
|
||||
supplyrecords.Add(JsonSerializer.Deserialize<SupplyRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in supplyrecords)
|
||||
@@ -297,7 +298,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
taxrecords.Add(System.Text.Json.JsonSerializer.Deserialize<TaxRecord>(reader["data"] as string));
|
||||
taxrecords.Add(JsonSerializer.Deserialize<TaxRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in taxrecords)
|
||||
@@ -360,7 +361,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
userconfigrecords.Add(System.Text.Json.JsonSerializer.Deserialize<UserConfigData>(reader["data"] as string));
|
||||
userconfigrecords.Add(JsonSerializer.Deserialize<UserConfigData>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in userconfigrecords)
|
||||
@@ -404,7 +405,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
extrafields.Add(System.Text.Json.JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string));
|
||||
extrafields.Add(JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in extrafields)
|
||||
@@ -418,24 +419,24 @@ namespace CarCareTracker.Controllers
|
||||
#endregion
|
||||
var destFilePath = $"{fullFolderPath}.zip";
|
||||
ZipFile.CreateFromDirectory(fullFolderPath, destFilePath);
|
||||
return Json(new OperationResponse { Success = true, Message = $"/{tempFolder}.zip" });
|
||||
return Json(OperationResponse.Succeed($"/{tempFolder}.zip"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
return Json(OperationResponse.Failed());
|
||||
}
|
||||
}
|
||||
public IActionResult Import(string fileName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_configHelper.GetServerPostgresConnection()))
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "Postgres connection not set up" });
|
||||
return Json(OperationResponse.Failed("Postgres connection not set up"));
|
||||
}
|
||||
var fullFileName = _fileHelper.GetFullFilePath(fileName);
|
||||
if (string.IsNullOrWhiteSpace(fullFileName))
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
return Json(OperationResponse.Failed());
|
||||
}
|
||||
try
|
||||
{
|
||||
@@ -475,7 +476,7 @@ namespace CarCareTracker.Controllers
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicle.Id);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(vehicle));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(vehicle));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -491,7 +492,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -507,7 +508,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -523,7 +524,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -541,7 +542,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -557,7 +558,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -573,7 +574,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -589,7 +590,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -607,7 +608,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -623,7 +624,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -639,7 +640,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -655,7 +656,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -706,7 +707,7 @@ namespace CarCareTracker.Controllers
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -738,17 +739,17 @@ namespace CarCareTracker.Controllers
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
return Json(new OperationResponse { Success = true, Message = "Data Imported Successfully" });
|
||||
return Json(OperationResponse.Succeed("Data Imported Successfully"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
return Json(OperationResponse.Failed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,11 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult SaveGasRecordToVehicleId(GasRecordInput gasRecord)
|
||||
{
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), gasRecord.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
if (gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||
{
|
||||
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||
@@ -49,10 +54,11 @@ namespace CarCareTracker.Controllers
|
||||
var result = _gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord.ToGasRecord());
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), gasRecord.VehicleId, User.Identity.Name, $"{(gasRecord.Id == default ? "Created" : "Edited")} Gas Record - Mileage: {gasRecord.Mileage.ToString()}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(gasRecord.ToGasRecord(), gasRecord.Id == default ? "gasrecord.add" : "gasrecord.update", User.Identity.Name));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
public IActionResult GetAddGasRecordPartialView(int vehicleId)
|
||||
{
|
||||
@@ -65,6 +71,11 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetGasRecordForEditById(int gasRecordId)
|
||||
{
|
||||
var result = _gasRecordDataAccess.GetGasRecordById(gasRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||
{
|
||||
return Redirect("/Error/Unauthorized");
|
||||
}
|
||||
var convertedResult = new GasRecordInput
|
||||
{
|
||||
Id = result.Id,
|
||||
@@ -91,14 +102,25 @@ namespace CarCareTracker.Controllers
|
||||
};
|
||||
return PartialView("_GasModal", viewModel);
|
||||
}
|
||||
private bool DeleteGasRecordWithChecks(int gasRecordId)
|
||||
{
|
||||
var existingRecord = _gasRecordDataAccess.GetGasRecordById(gasRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var result = _gasRecordDataAccess.DeleteGasRecordById(existingRecord.Id);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(existingRecord, "gasrecord.delete", User.Identity.Name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DeleteGasRecordById(int gasRecordId)
|
||||
{
|
||||
var result = _gasRecordDataAccess.DeleteGasRecordById(gasRecordId);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Gas Record - Id: {gasRecordId}");
|
||||
}
|
||||
var result = DeleteGasRecordWithChecks(gasRecordId);
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
|
||||
@@ -3,6 +3,7 @@ using CarCareTracker.Helper;
|
||||
using CarCareTracker.MapProfile;
|
||||
using CarCareTracker.Models;
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Globalization;
|
||||
|
||||
@@ -24,7 +25,7 @@ namespace CarCareTracker.Controllers
|
||||
return Json(false);
|
||||
}
|
||||
string uploadDirectory = "temp/";
|
||||
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
|
||||
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
|
||||
if (!Directory.Exists(uploadPath))
|
||||
Directory.CreateDirectory(uploadPath);
|
||||
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||
@@ -259,7 +260,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
using (var reader = new StreamReader(fullFileName))
|
||||
{
|
||||
var config = new CsvHelper.Configuration.CsvConfiguration(CultureInfo.InvariantCulture);
|
||||
var config = new CsvConfiguration(CultureInfo.InvariantCulture);
|
||||
config.MissingFieldFound = null;
|
||||
config.HeaderValidated = null;
|
||||
config.PrepareHeaderForMatch = args => { return args.Header.Trim().ToLower(); };
|
||||
@@ -272,13 +273,22 @@ namespace CarCareTracker.Controllers
|
||||
var requiredExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)mode).ExtraFields.Where(x => x.IsRequired).Select(y => y.Name);
|
||||
foreach (ImportModel importModel in records)
|
||||
{
|
||||
var parsedDate = DateTime.Now.Date;
|
||||
if (!string.IsNullOrWhiteSpace(importModel.Date))
|
||||
{
|
||||
parsedDate = DateTime.Parse(importModel.Date);
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(importModel.Day) && !string.IsNullOrWhiteSpace(importModel.Month) && !string.IsNullOrWhiteSpace(importModel.Year))
|
||||
{
|
||||
parsedDate = new DateTime(int.Parse(importModel.Year), int.Parse(importModel.Month), int.Parse(importModel.Day));
|
||||
}
|
||||
if (mode == ImportMode.GasRecord)
|
||||
{
|
||||
//convert to gas model.
|
||||
var convertedRecord = new GasRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(importModel.Date),
|
||||
Date = parsedDate,
|
||||
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||
Gallons = decimal.Parse(importModel.FuelConsumed, NumberStyles.Any),
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
@@ -334,9 +344,9 @@ namespace CarCareTracker.Controllers
|
||||
var convertedRecord = new ServiceRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(importModel.Date),
|
||||
Date = parsedDate,
|
||||
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Service Record on {importModel.Date}" : importModel.Description,
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Service Record on {parsedDate.ToShortDateString()}" : importModel.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||
@@ -359,7 +369,7 @@ namespace CarCareTracker.Controllers
|
||||
var convertedRecord = new OdometerRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(importModel.Date),
|
||||
Date = parsedDate,
|
||||
InitialMileage = string.IsNullOrWhiteSpace(importModel.InitialOdometer) ? 0 : decimal.ToInt32(decimal.Parse(importModel.InitialOdometer, NumberStyles.Any)),
|
||||
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
@@ -393,9 +403,9 @@ namespace CarCareTracker.Controllers
|
||||
var convertedRecord = new CollisionRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(importModel.Date),
|
||||
Date = parsedDate,
|
||||
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Repair Record on {importModel.Date}" : importModel.Description,
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Repair Record on {parsedDate.ToShortDateString()}" : importModel.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||
@@ -418,9 +428,9 @@ namespace CarCareTracker.Controllers
|
||||
var convertedRecord = new UpgradeRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(importModel.Date),
|
||||
Date = parsedDate,
|
||||
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Upgrade Record on {importModel.Date}" : importModel.Description,
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Upgrade Record on {parsedDate.ToShortDateString()}" : importModel.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||
@@ -443,7 +453,7 @@ namespace CarCareTracker.Controllers
|
||||
var convertedRecord = new SupplyRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(importModel.Date),
|
||||
Date = parsedDate,
|
||||
PartNumber = importModel.PartNumber,
|
||||
PartSupplier = importModel.PartSupplier,
|
||||
Quantity = decimal.Parse(importModel.PartQuantity, NumberStyles.Any),
|
||||
@@ -460,8 +470,8 @@ namespace CarCareTracker.Controllers
|
||||
var convertedRecord = new TaxRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(importModel.Date),
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Tax Record on {importModel.Date}" : importModel.Description,
|
||||
Date = parsedDate,
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Tax Record on {parsedDate.ToShortDateString()}" : importModel.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetNotesByVehicleId(int vehicleId)
|
||||
{
|
||||
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
|
||||
result = result.OrderByDescending(x => x.Pinned).ToList();
|
||||
result = result.OrderByDescending(x => x.Pinned).ThenBy(x => x.Description).ToList();
|
||||
return PartialView("_Notes", result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
@@ -26,11 +26,17 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult SaveNoteToVehicleId(Note note)
|
||||
{
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), note.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
note.Files = note.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||
bool isCreate = note.Id == default; //needed here since Notes don't use an input object.
|
||||
var result = _noteDataAccess.SaveNoteToVehicle(note);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), note.VehicleId, User.Identity.Name, $"{(note.Id == default ? "Created" : "Edited")} Note - Description: {note.Description}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromNoteRecord(note, isCreate ? "noterecord.add" : "noterecord.update", User.Identity.Name));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -43,16 +49,32 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetNoteForEditById(int noteId)
|
||||
{
|
||||
var result = _noteDataAccess.GetNoteById(noteId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||
{
|
||||
return Redirect("/Error/Unauthorized");
|
||||
}
|
||||
return PartialView("_NoteModal", result);
|
||||
}
|
||||
private bool DeleteNoteWithChecks(int noteId)
|
||||
{
|
||||
var existingRecord = _noteDataAccess.GetNoteById(noteId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var result = _noteDataAccess.DeleteNoteById(existingRecord.Id);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromNoteRecord(existingRecord, "noterecord.delete", User.Identity.Name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DeleteNoteById(int noteId)
|
||||
{
|
||||
var result = _noteDataAccess.DeleteNoteById(noteId);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Note - Id: {noteId}");
|
||||
}
|
||||
var result = DeleteNoteWithChecks(noteId);
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
|
||||
@@ -39,15 +39,21 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult SaveOdometerRecordToVehicleId(OdometerRecordInput odometerRecord)
|
||||
{
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), odometerRecord.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
//move files from temp.
|
||||
odometerRecord.Files = odometerRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord.ToOdometerRecord());
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), odometerRecord.VehicleId, User.Identity.Name, $"{(odometerRecord.Id == default ? "Created" : "Edited")} Odometer Record - Mileage: {odometerRecord.Mileage.ToString()}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(odometerRecord.ToOdometerRecord(), odometerRecord.Id == default ? "odometerrecord.add" : "odometerrecord.update", User.Identity.Name));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
public IActionResult GetAddOdometerRecordPartialView(int vehicleId)
|
||||
{
|
||||
@@ -125,6 +131,11 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetOdometerRecordForEditById(int odometerRecordId)
|
||||
{
|
||||
var result = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||
{
|
||||
return Redirect("/Error/Unauthorized");
|
||||
}
|
||||
//convert to Input object.
|
||||
var convertedResult = new OdometerRecordInput
|
||||
{
|
||||
@@ -140,14 +151,25 @@ namespace CarCareTracker.Controllers
|
||||
};
|
||||
return PartialView("_OdometerRecordModal", convertedResult);
|
||||
}
|
||||
private bool DeleteOdometerRecordWithChecks(int odometerRecordId)
|
||||
{
|
||||
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(existingRecord.Id);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(existingRecord, "odometerrecord.delete", User.Identity.Name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DeleteOdometerRecordById(int odometerRecordId)
|
||||
{
|
||||
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(odometerRecordId);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Odometer Record - Id: {odometerRecordId}");
|
||||
}
|
||||
var result = DeleteOdometerRecordWithChecks(odometerRecordId);
|
||||
return Json(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult SavePlanRecordToVehicleId(PlanRecordInput planRecord)
|
||||
{
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), planRecord.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
//populate createdDate
|
||||
if (planRecord.Id == default)
|
||||
{
|
||||
@@ -27,31 +32,40 @@ namespace CarCareTracker.Controllers
|
||||
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||
if (planRecord.Supplies.Any())
|
||||
{
|
||||
planRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(planRecord.Supplies, DateTime.Parse(planRecord.DateCreated), planRecord.Description);
|
||||
planRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(planRecord.Supplies, DateTime.Parse(planRecord.DateCreated), planRecord.Description));
|
||||
if (planRecord.CopySuppliesAttachment)
|
||||
{
|
||||
planRecord.Files.AddRange(GetSuppliesAttachments(planRecord.Supplies));
|
||||
}
|
||||
}
|
||||
if (planRecord.DeletedRequisitionHistory.Any())
|
||||
{
|
||||
_vehicleLogic.RestoreSupplyRecordsByUsage(planRecord.DeletedRequisitionHistory, planRecord.Description);
|
||||
}
|
||||
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), planRecord.VehicleId, User.Identity.Name, $"{(planRecord.Id == default ? "Created" : "Edited")} Plan Record - Description: {planRecord.Description}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromPlanRecord(planRecord.ToPlanRecord(), planRecord.Id == default ? "planrecord.add" : "planrecord.update", User.Identity.Name));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult SavePlanRecordTemplateToVehicleId(PlanRecordInput planRecord)
|
||||
{
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), planRecord.VehicleId))
|
||||
{
|
||||
return Json(OperationResponse.Failed("Access Denied"));
|
||||
}
|
||||
//check if template name already taken.
|
||||
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(planRecord.VehicleId).Where(x => x.Description == planRecord.Description).Any();
|
||||
if (planRecord.Id == default && existingRecord)
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "A template with that description already exists for this vehicle" });
|
||||
return Json(OperationResponse.Failed("A template with that description already exists for this vehicle"));
|
||||
}
|
||||
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||
var result = _planRecordTemplateDataAccess.SavePlanRecordTemplateToVehicle(planRecord);
|
||||
return Json(new OperationResponse { Success = result, Message = result ? "Template Added" : StaticHelper.GenericErrorMessage });
|
||||
return Json(OperationResponse.Conditional(result, "Template Added", string.Empty));
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
@@ -63,24 +77,66 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult DeletePlanRecordTemplateById(int planRecordTemplateId)
|
||||
{
|
||||
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
|
||||
if (existingRecord.Id == default)
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
var result = _planRecordTemplateDataAccess.DeletePlanRecordTemplateById(planRecordTemplateId);
|
||||
return Json(result);
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult OrderPlanSupplies(int planRecordTemplateId)
|
||||
{
|
||||
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
|
||||
if (existingRecord.Id == default)
|
||||
{
|
||||
return Json(OperationResponse.Failed("Unable to find template"));
|
||||
}
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return Json(OperationResponse.Failed("Access Denied"));
|
||||
}
|
||||
if (existingRecord.Supplies.Any())
|
||||
{
|
||||
var suppliesToOrder = CheckSupplyRecordsAvailability(existingRecord.Supplies);
|
||||
return PartialView("_PlanOrderSupplies", suppliesToOrder);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Json(OperationResponse.Failed("Template has No Supplies"));
|
||||
}
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult ConvertPlanRecordTemplateToPlanRecord(int planRecordTemplateId)
|
||||
{
|
||||
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
|
||||
if (existingRecord.Id == default)
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "Unable to find template" });
|
||||
return Json(OperationResponse.Failed("Unable to find template"));
|
||||
}
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return Json(OperationResponse.Failed("Access Denied"));
|
||||
}
|
||||
if (existingRecord.Supplies.Any())
|
||||
{
|
||||
//check if all supplies are available
|
||||
var supplyAvailability = CheckSupplyRecordsAvailability(existingRecord.Supplies);
|
||||
if (supplyAvailability.Any())
|
||||
if (supplyAvailability.Any(x => x.Missing))
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = string.Join("<br>", supplyAvailability) });
|
||||
return Json(OperationResponse.Failed("Missing Supplies, Please Delete This Template and Recreate It."));
|
||||
}
|
||||
else if (supplyAvailability.Any(x => x.Insufficient))
|
||||
{
|
||||
return Json(OperationResponse.Failed("Insufficient Supplies"));
|
||||
}
|
||||
}
|
||||
if (existingRecord.ReminderRecordId != default)
|
||||
@@ -89,7 +145,7 @@ namespace CarCareTracker.Controllers
|
||||
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(existingRecord.ReminderRecordId);
|
||||
if (existingReminder is null || existingReminder.Id == default || !existingReminder.IsRecurring)
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "Missing or Non-recurring Reminder, Please Delete This Template and Recreate It." });
|
||||
return Json(OperationResponse.Failed("Missing or Non-recurring Reminder, Please Delete This Template and Recreate It."));
|
||||
}
|
||||
}
|
||||
//populate createdDate
|
||||
@@ -105,7 +161,7 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
}
|
||||
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord.ToPlanRecord());
|
||||
return Json(new OperationResponse { Success = result, Message = result ? "Plan Record Added" : StaticHelper.GenericErrorMessage });
|
||||
return Json(OperationResponse.Conditional(result, "Plan Record Added", string.Empty));
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetAddPlanRecordPartialView()
|
||||
@@ -125,7 +181,16 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult UpdatePlanRecordProgress(int planRecordId, PlanProgress planProgress, int odometer = 0)
|
||||
{
|
||||
if (planRecordId == default)
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
existingRecord.Progress = planProgress;
|
||||
existingRecord.DateModified = DateTime.Now;
|
||||
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
|
||||
@@ -209,6 +274,11 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetPlanRecordForEditById(int planRecordId)
|
||||
{
|
||||
var result = _planRecordDataAccess.GetPlanRecordById(planRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||
{
|
||||
return Redirect("/Error/Unauthorized");
|
||||
}
|
||||
//convert to Input object.
|
||||
var convertedResult = new PlanRecordInput
|
||||
{
|
||||
@@ -232,10 +302,21 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult DeletePlanRecordById(int planRecordId)
|
||||
{
|
||||
var result = _planRecordDataAccess.DeletePlanRecordById(planRecordId);
|
||||
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
//restore any requisitioned supplies if it has not been converted to other record types.
|
||||
if (existingRecord.RequisitionHistory.Any() && existingRecord.Progress != PlanProgress.Done)
|
||||
{
|
||||
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
||||
}
|
||||
var result = _planRecordDataAccess.DeletePlanRecordById(existingRecord.Id);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Plan Record - Id: {planRecordId}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromPlanRecord(existingRecord, "planrecord.delete", User.Identity.Name));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace CarCareTracker.Controllers
|
||||
result = result.OrderByDescending(x => x.Urgency).ToList();
|
||||
return PartialView("_ReminderRecords", result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
public IActionResult GetRecurringReminderRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
@@ -105,10 +106,15 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult SaveReminderRecordToVehicleId(ReminderRecordInput reminderRecord)
|
||||
{
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), reminderRecord.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
var result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord.ToReminderRecord());
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), reminderRecord.VehicleId, User.Identity.Name, $"{(reminderRecord.Id == default ? "Created" : "Edited")} Reminder - Description: {reminderRecord.Description}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(reminderRecord.ToReminderRecord(), reminderRecord.Id == default ? "reminderrecord.add" : "reminderrecord.update", User.Identity.Name));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -128,6 +134,11 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetReminderRecordForEditById(int reminderRecordId)
|
||||
{
|
||||
var result = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||
{
|
||||
return Redirect("/Error/Unauthorized");
|
||||
}
|
||||
//convert to Input object.
|
||||
var convertedResult = new ReminderRecordInput
|
||||
{
|
||||
@@ -145,18 +156,30 @@ namespace CarCareTracker.Controllers
|
||||
ReminderMonthInterval = result.ReminderMonthInterval,
|
||||
CustomMileageInterval = result.CustomMileageInterval,
|
||||
CustomMonthInterval = result.CustomMonthInterval,
|
||||
CustomMonthIntervalUnit = result.CustomMonthIntervalUnit,
|
||||
Tags = result.Tags
|
||||
};
|
||||
return PartialView("_ReminderRecordModal", convertedResult);
|
||||
}
|
||||
private bool DeleteReminderRecordWithChecks(int reminderRecordId)
|
||||
{
|
||||
var existingRecord = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var result = _reminderRecordDataAccess.DeleteReminderRecordById(existingRecord.Id);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(existingRecord, "reminderrecord.delete", User.Identity.Name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DeleteReminderRecordById(int reminderRecordId)
|
||||
{
|
||||
var result = _reminderRecordDataAccess.DeleteReminderRecordById(reminderRecordId);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Reminder - Id: {reminderRecordId}");
|
||||
}
|
||||
var result = DeleteReminderRecordWithChecks(reminderRecordId);
|
||||
return Json(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult SaveCollisionRecordToVehicleId(CollisionRecordInput collisionRecord)
|
||||
{
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), collisionRecord.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
if (collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||
{
|
||||
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||
@@ -40,12 +45,16 @@ namespace CarCareTracker.Controllers
|
||||
collisionRecord.Files = collisionRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||
if (collisionRecord.Supplies.Any())
|
||||
{
|
||||
collisionRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(collisionRecord.Supplies, DateTime.Parse(collisionRecord.Date), collisionRecord.Description);
|
||||
collisionRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(collisionRecord.Supplies, DateTime.Parse(collisionRecord.Date), collisionRecord.Description));
|
||||
if (collisionRecord.CopySuppliesAttachment)
|
||||
{
|
||||
collisionRecord.Files.AddRange(GetSuppliesAttachments(collisionRecord.Supplies));
|
||||
}
|
||||
}
|
||||
if (collisionRecord.DeletedRequisitionHistory.Any())
|
||||
{
|
||||
_vehicleLogic.RestoreSupplyRecordsByUsage(collisionRecord.DeletedRequisitionHistory, collisionRecord.Description);
|
||||
}
|
||||
//push back any reminders
|
||||
if (collisionRecord.ReminderRecordId.Any())
|
||||
{
|
||||
@@ -57,7 +66,7 @@ namespace CarCareTracker.Controllers
|
||||
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord());
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), collisionRecord.VehicleId, User.Identity.Name, $"{(collisionRecord.Id == default ? "Created" : "Edited")} Repair Record - Description: {collisionRecord.Description}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(collisionRecord.ToCollisionRecord(), collisionRecord.Id == default ? "repairrecord.add" : "repairrecord.update", User.Identity.Name));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -70,6 +79,11 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetCollisionRecordForEditById(int collisionRecordId)
|
||||
{
|
||||
var result = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||
{
|
||||
return Redirect("/Error/Unauthorized");
|
||||
}
|
||||
//convert to Input object.
|
||||
var convertedResult = new CollisionRecordInput
|
||||
{
|
||||
@@ -87,14 +101,30 @@ namespace CarCareTracker.Controllers
|
||||
};
|
||||
return PartialView("_CollisionRecordModal", convertedResult);
|
||||
}
|
||||
private bool DeleteCollisionRecordWithChecks(int collisionRecordId)
|
||||
{
|
||||
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
//restore any requisitioned supplies.
|
||||
if (existingRecord.RequisitionHistory.Any())
|
||||
{
|
||||
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
||||
}
|
||||
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(existingRecord.Id);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "repairrecord.delete", User.Identity.Name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DeleteCollisionRecordById(int collisionRecordId)
|
||||
{
|
||||
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(collisionRecordId);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Repair Record - Id: {collisionRecordId}");
|
||||
}
|
||||
var result = DeleteCollisionRecordWithChecks(collisionRecordId);
|
||||
return Json(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace CarCareTracker.Controllers
|
||||
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||
var userConfig = _config.GetUserConfig(User);
|
||||
var viewModel = new ReportViewModel();
|
||||
//check if custom widgets are configured
|
||||
viewModel.CustomWidgetsConfigured = _fileHelper.WidgetsExist();
|
||||
//get totalCostMakeUp
|
||||
viewModel.CostMakeUpForVehicle = new CostMakeUpForVehicle
|
||||
{
|
||||
@@ -105,7 +107,7 @@ namespace CarCareTracker.Controllers
|
||||
}).ToList();
|
||||
if (invertedFuelMileageUnit)
|
||||
{
|
||||
foreach(CostForVehicleByMonth monthMileage in monthlyMileageData)
|
||||
foreach (CostForVehicleByMonth monthMileage in monthlyMileageData)
|
||||
{
|
||||
if (monthMileage.Cost != default)
|
||||
{
|
||||
@@ -113,7 +115,7 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
}
|
||||
}
|
||||
var mpgViewModel = new MPGForVehicleByMonth {
|
||||
var mpgViewModel = new MPGForVehicleByMonth {
|
||||
CostData = monthlyMileageData,
|
||||
Unit = invertedFuelMileageUnit ? preferredFuelMileageUnit : fuelEconomyMileageUnit,
|
||||
SortedCostData = (userConfig.UseMPG || invertedFuelMileageUnit) ? monthlyMileageData.OrderByDescending(x => x.Cost).ToList() : monthlyMileageData.OrderBy(x => x.Cost).ToList()
|
||||
@@ -194,7 +196,7 @@ namespace CarCareTracker.Controllers
|
||||
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||
var userConfig = _config.GetUserConfig(User);
|
||||
var totalDistanceTraveled = maxMileage - minMileage;
|
||||
var totalDays = _vehicleLogic.GetOwnershipDays(vehicleData.PurchaseDate, vehicleData.SoldDate, serviceRecords, collisionRecords, gasRecords, upgradeRecords, odometerRecords, taxRecords);
|
||||
var totalDays = _vehicleLogic.GetOwnershipDays(vehicleData.PurchaseDate, vehicleData.SoldDate, year, serviceRecords, collisionRecords, gasRecords, upgradeRecords, odometerRecords, taxRecords);
|
||||
var viewModel = new CostTableForVehicle
|
||||
{
|
||||
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
|
||||
@@ -309,23 +311,95 @@ namespace CarCareTracker.Controllers
|
||||
var result = _fileHelper.MakeAttachmentsExport(attachmentData);
|
||||
if (string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
return Json(OperationResponse.Failed());
|
||||
}
|
||||
return Json(new OperationResponse { Success = true, Message = result });
|
||||
return Json(OperationResponse.Succeed(result));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "No Attachments Found" });
|
||||
return Json(OperationResponse.Failed("No Attachments Found"));
|
||||
}
|
||||
}
|
||||
public IActionResult GetReportParameters()
|
||||
{
|
||||
var viewModel = new ReportParameter() {
|
||||
VisibleColumns = new List<string> {
|
||||
nameof(GenericReportModel.DataType),
|
||||
nameof(GenericReportModel.Date),
|
||||
nameof(GenericReportModel.Odometer),
|
||||
nameof(GenericReportModel.Description),
|
||||
nameof(GenericReportModel.Cost),
|
||||
nameof(GenericReportModel.Notes)
|
||||
}
|
||||
};
|
||||
//get all extra fields from service records, repairs, upgrades, and tax records.
|
||||
var recordTypes = new List<int>() { 0, 1, 3, 4 };
|
||||
var extraFields = new List<string>();
|
||||
foreach(int recordType in recordTypes)
|
||||
{
|
||||
extraFields.AddRange(_extraFieldDataAccess.GetExtraFieldsById(recordType).ExtraFields.Select(x => x.Name));
|
||||
}
|
||||
viewModel.ExtraFields = extraFields.Distinct().ToList();
|
||||
|
||||
return PartialView("_ReportParameters", viewModel);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
public IActionResult GetVehicleHistory(int vehicleId)
|
||||
public IActionResult GetVehicleHistory(int vehicleId, ReportParameter reportParameter)
|
||||
{
|
||||
var vehicleHistory = new VehicleHistoryViewModel();
|
||||
vehicleHistory.ReportParameters = reportParameter;
|
||||
vehicleHistory.VehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleId);
|
||||
var vehicleRecords = _vehicleLogic.GetVehicleRecords(vehicleId);
|
||||
bool useMPG = _config.GetUserConfig(User).UseMPG;
|
||||
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
|
||||
var gasViewModels = _gasHelper.GetGasRecordViewModels(vehicleRecords.GasRecords, useMPG, useUKMPG);
|
||||
//filter by tags
|
||||
if (reportParameter.Tags.Any())
|
||||
{
|
||||
if (reportParameter.TagFilter == TagFilter.Exclude)
|
||||
{
|
||||
vehicleRecords.OdometerRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
vehicleRecords.ServiceRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
vehicleRecords.CollisionRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
vehicleRecords.UpgradeRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
vehicleRecords.TaxRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
gasViewModels.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
vehicleRecords.GasRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
}
|
||||
else if (reportParameter.TagFilter == TagFilter.IncludeOnly)
|
||||
{
|
||||
vehicleRecords.OdometerRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
vehicleRecords.ServiceRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
vehicleRecords.CollisionRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
vehicleRecords.UpgradeRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
vehicleRecords.TaxRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
gasViewModels.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
vehicleRecords.GasRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||
}
|
||||
}
|
||||
//filter by date range.
|
||||
if (reportParameter.FilterByDateRange && !string.IsNullOrWhiteSpace(reportParameter.StartDate) && !string.IsNullOrWhiteSpace(reportParameter.EndDate))
|
||||
{
|
||||
var startDate = DateTime.Parse(reportParameter.StartDate).Date;
|
||||
var endDate = DateTime.Parse(reportParameter.EndDate).Date;
|
||||
//validate date range
|
||||
if (endDate >= startDate) //allow for same day.
|
||||
{
|
||||
vehicleHistory.StartDate = reportParameter.StartDate;
|
||||
vehicleHistory.EndDate = reportParameter.EndDate;
|
||||
//remove all records with dates after the end date and dates before the start date.
|
||||
vehicleRecords.OdometerRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||
vehicleRecords.ServiceRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||
vehicleRecords.CollisionRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||
vehicleRecords.UpgradeRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||
vehicleRecords.TaxRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||
gasViewModels.RemoveAll(x => DateTime.Parse(x.Date).Date > endDate || DateTime.Parse(x.Date).Date < startDate);
|
||||
vehicleRecords.GasRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||
}
|
||||
}
|
||||
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
|
||||
vehicleHistory.Odometer = maxMileage.ToString("N0");
|
||||
var minMileage = _vehicleLogic.GetMinMileage(vehicleId);
|
||||
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
|
||||
var distanceTraveled = maxMileage - minMileage;
|
||||
if (!string.IsNullOrWhiteSpace(vehicleHistory.VehicleData.PurchaseDate))
|
||||
{
|
||||
@@ -362,17 +436,10 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
}
|
||||
List<GenericReportModel> reportData = new List<GenericReportModel>();
|
||||
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||
bool useMPG = _config.GetUserConfig(User).UseMPG;
|
||||
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
|
||||
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
|
||||
vehicleHistory.DistanceUnit = vehicleHistory.VehicleData.UseHours ? "h" : useMPG ? "mi." : "km";
|
||||
vehicleHistory.TotalGasCost = gasRecords.Sum(x => x.Cost);
|
||||
vehicleHistory.TotalCost = serviceRecords.Sum(x => x.Cost) + repairRecords.Sum(x => x.Cost) + upgradeRecords.Sum(x => x.Cost) + taxRecords.Sum(x => x.Cost);
|
||||
vehicleHistory.TotalGasCost = gasViewModels.Sum(x => x.Cost);
|
||||
vehicleHistory.TotalCost = vehicleRecords.ServiceRecords.Sum(x => x.Cost) + vehicleRecords.CollisionRecords.Sum(x => x.Cost) + vehicleRecords.UpgradeRecords.Sum(x => x.Cost) + vehicleRecords.TaxRecords.Sum(x => x.Cost);
|
||||
if (distanceTraveled != default)
|
||||
{
|
||||
vehicleHistory.DistanceTraveled = distanceTraveled.ToString("N0");
|
||||
@@ -380,7 +447,6 @@ namespace CarCareTracker.Controllers
|
||||
vehicleHistory.TotalGasCostPerMile = vehicleHistory.TotalGasCost / distanceTraveled;
|
||||
}
|
||||
var averageMPG = "0";
|
||||
var gasViewModels = _gasHelper.GetGasRecordViewModels(gasRecords, useMPG, useUKMPG);
|
||||
if (gasViewModels.Any())
|
||||
{
|
||||
averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels, useMPG);
|
||||
@@ -399,42 +465,46 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
vehicleHistory.MPG = $"{averageMPG} {fuelEconomyMileageUnit}";
|
||||
//insert servicerecords
|
||||
reportData.AddRange(serviceRecords.Select(x => new GenericReportModel
|
||||
reportData.AddRange(vehicleRecords.ServiceRecords.Select(x => new GenericReportModel
|
||||
{
|
||||
Date = x.Date,
|
||||
Odometer = x.Mileage,
|
||||
Description = x.Description,
|
||||
Notes = x.Notes,
|
||||
Cost = x.Cost,
|
||||
DataType = ImportMode.ServiceRecord
|
||||
DataType = ImportMode.ServiceRecord,
|
||||
ExtraFields = x.ExtraFields
|
||||
}));
|
||||
//repair records
|
||||
reportData.AddRange(repairRecords.Select(x => new GenericReportModel
|
||||
reportData.AddRange(vehicleRecords.CollisionRecords.Select(x => new GenericReportModel
|
||||
{
|
||||
Date = x.Date,
|
||||
Odometer = x.Mileage,
|
||||
Description = x.Description,
|
||||
Notes = x.Notes,
|
||||
Cost = x.Cost,
|
||||
DataType = ImportMode.RepairRecord
|
||||
DataType = ImportMode.RepairRecord,
|
||||
ExtraFields = x.ExtraFields
|
||||
}));
|
||||
reportData.AddRange(upgradeRecords.Select(x => new GenericReportModel
|
||||
reportData.AddRange(vehicleRecords.UpgradeRecords.Select(x => new GenericReportModel
|
||||
{
|
||||
Date = x.Date,
|
||||
Odometer = x.Mileage,
|
||||
Description = x.Description,
|
||||
Notes = x.Notes,
|
||||
Cost = x.Cost,
|
||||
DataType = ImportMode.UpgradeRecord
|
||||
DataType = ImportMode.UpgradeRecord,
|
||||
ExtraFields = x.ExtraFields
|
||||
}));
|
||||
reportData.AddRange(taxRecords.Select(x => new GenericReportModel
|
||||
reportData.AddRange(vehicleRecords.TaxRecords.Select(x => new GenericReportModel
|
||||
{
|
||||
Date = x.Date,
|
||||
Odometer = 0,
|
||||
Description = x.Description,
|
||||
Notes = x.Notes,
|
||||
Cost = x.Cost,
|
||||
DataType = ImportMode.TaxRecord
|
||||
DataType = ImportMode.TaxRecord,
|
||||
ExtraFields = x.ExtraFields
|
||||
}));
|
||||
vehicleHistory.VehicleHistory = reportData.OrderBy(x => x.Date).ThenBy(x => x.Odometer).ToList();
|
||||
return PartialView("_VehicleHistory", vehicleHistory);
|
||||
@@ -528,5 +598,60 @@ namespace CarCareTracker.Controllers
|
||||
}).ToList();
|
||||
return PartialView("_GasCostByMonthReport", groupedRecord);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
public IActionResult GetCostByMonthAndYearByVehicle(int vehicleId, List<ImportMode> selectedMetrics, int year = 0)
|
||||
{
|
||||
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
|
||||
if (selectedMetrics.Contains(ImportMode.ServiceRecord))
|
||||
{
|
||||
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, year, true));
|
||||
}
|
||||
if (selectedMetrics.Contains(ImportMode.RepairRecord))
|
||||
{
|
||||
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||
allCosts.AddRange(_reportHelper.GetRepairRecordSum(repairRecords, year, true));
|
||||
}
|
||||
if (selectedMetrics.Contains(ImportMode.UpgradeRecord))
|
||||
{
|
||||
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, year, true));
|
||||
}
|
||||
if (selectedMetrics.Contains(ImportMode.GasRecord))
|
||||
{
|
||||
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||
allCosts.AddRange(_reportHelper.GetGasRecordSum(gasRecords, year, true));
|
||||
}
|
||||
if (selectedMetrics.Contains(ImportMode.TaxRecord))
|
||||
{
|
||||
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, year, true));
|
||||
}
|
||||
if (selectedMetrics.Contains(ImportMode.OdometerRecord))
|
||||
{
|
||||
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, year, true));
|
||||
}
|
||||
var groupedRecord = allCosts.GroupBy(x => new { x.MonthName, x.MonthId, x.Year }).OrderByDescending(x=>x.Key.Year).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
Year = x.Key.Year,
|
||||
MonthName = x.Key.MonthName,
|
||||
Cost = x.Sum(y => y.Cost),
|
||||
DistanceTraveled = x.Max(y => y.DistanceTraveled),
|
||||
MonthId = x.Key.MonthId
|
||||
}).ToList();
|
||||
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||
var userConfig = _config.GetUserConfig(User);
|
||||
var viewModel = new CostDistanceTableForVehicle { CostData = groupedRecord };
|
||||
viewModel.DistanceUnit = vehicleData.UseHours ? "h" : userConfig.UseMPG ? "mi." : "km";
|
||||
return PartialView("_CostDistanceTableReport", viewModel);
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetAdditionalWidgets()
|
||||
{
|
||||
var widgets = _fileHelper.GetWidgets();
|
||||
return PartialView("_ReportWidgets", widgets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult SaveServiceRecordToVehicleId(ServiceRecordInput serviceRecord)
|
||||
{
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), serviceRecord.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
if (serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||
{
|
||||
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||
@@ -40,12 +45,16 @@ namespace CarCareTracker.Controllers
|
||||
serviceRecord.Files = serviceRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||
if (serviceRecord.Supplies.Any())
|
||||
{
|
||||
serviceRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(serviceRecord.Supplies, DateTime.Parse(serviceRecord.Date), serviceRecord.Description);
|
||||
serviceRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(serviceRecord.Supplies, DateTime.Parse(serviceRecord.Date), serviceRecord.Description));
|
||||
if (serviceRecord.CopySuppliesAttachment)
|
||||
{
|
||||
serviceRecord.Files.AddRange(GetSuppliesAttachments(serviceRecord.Supplies));
|
||||
}
|
||||
}
|
||||
if (serviceRecord.DeletedRequisitionHistory.Any())
|
||||
{
|
||||
_vehicleLogic.RestoreSupplyRecordsByUsage(serviceRecord.DeletedRequisitionHistory, serviceRecord.Description);
|
||||
}
|
||||
//push back any reminders
|
||||
if (serviceRecord.ReminderRecordId.Any())
|
||||
{
|
||||
@@ -57,7 +66,7 @@ namespace CarCareTracker.Controllers
|
||||
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), serviceRecord.VehicleId, User.Identity.Name, $"{(serviceRecord.Id == default ? "Created" : "Edited")} Service Record - Description: {serviceRecord.Description}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(serviceRecord.ToServiceRecord(), serviceRecord.Id == default ? "servicerecord.add" : "servicerecord.update", User.Identity.Name));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -70,6 +79,11 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetServiceRecordForEditById(int serviceRecordId)
|
||||
{
|
||||
var result = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||
{
|
||||
return Redirect("/Error/Unauthorized");
|
||||
}
|
||||
//convert to Input object.
|
||||
var convertedResult = new ServiceRecordInput
|
||||
{
|
||||
@@ -87,14 +101,30 @@ namespace CarCareTracker.Controllers
|
||||
};
|
||||
return PartialView("_ServiceRecordModal", convertedResult);
|
||||
}
|
||||
private bool DeleteServiceRecordWithChecks(int serviceRecordId)
|
||||
{
|
||||
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
//restore any requisitioned supplies.
|
||||
if (existingRecord.RequisitionHistory.Any())
|
||||
{
|
||||
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
||||
}
|
||||
var result = _serviceRecordDataAccess.DeleteServiceRecordById(existingRecord.Id);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "servicerecord.delete", User.Identity.Name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DeleteServiceRecordById(int serviceRecordId)
|
||||
{
|
||||
var result = _serviceRecordDataAccess.DeleteServiceRecordById(serviceRecordId);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Service Record - Id: {serviceRecordId}");
|
||||
}
|
||||
var result = DeleteServiceRecordWithChecks(serviceRecordId);
|
||||
return Json(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,21 +7,21 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
public partial class VehicleController
|
||||
{
|
||||
private List<string> CheckSupplyRecordsAvailability(List<SupplyUsage> supplyUsage)
|
||||
private List<SupplyAvailability> CheckSupplyRecordsAvailability(List<SupplyUsage> supplyUsage)
|
||||
{
|
||||
//returns empty string if all supplies are available
|
||||
var result = new List<string>();
|
||||
var result = new List<SupplyAvailability>();
|
||||
foreach (SupplyUsage supply in supplyUsage)
|
||||
{
|
||||
//get supply record.
|
||||
var supplyData = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
|
||||
if (supplyData == null)
|
||||
{
|
||||
result.Add("Missing Supplies, Please Delete This Template and Recreate It.");
|
||||
result.Add(new SupplyAvailability { Missing = true });
|
||||
}
|
||||
else if (supply.Quantity > supplyData.Quantity)
|
||||
else
|
||||
{
|
||||
result.Add($"Insufficient Quantity for {supplyData.Description}, need: {supply.Quantity}, available: {supplyData.Quantity}");
|
||||
result.Add(new SupplyAvailability { Missing = false, Description = supplyData.Description, Required = supply.Quantity, InStock = supplyData.Quantity });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -58,6 +58,7 @@ namespace CarCareTracker.Controllers
|
||||
//create new requisitionrrecord
|
||||
var requisitionRecord = new SupplyUsageHistory
|
||||
{
|
||||
Id = supply.SupplyId,
|
||||
Date = dateRequisitioned,
|
||||
Description = usageDescription,
|
||||
Quantity = supply.Quantity,
|
||||
@@ -72,6 +73,7 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
public IActionResult GetSupplyRecordsByVehicleId(int vehicleId)
|
||||
@@ -148,7 +150,7 @@ namespace CarCareTracker.Controllers
|
||||
var result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(supplyRecord.ToSupplyRecord());
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), supplyRecord.VehicleId, User.Identity.Name, $"{(supplyRecord.Id == default ? "Created" : "Edited")} Supply Record - Description: {supplyRecord.Description}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromSupplyRecord(supplyRecord.ToSupplyRecord(), supplyRecord.Id == default ? "supplyrecord.add" : "supplyrecord.update", User.Identity.Name));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -161,6 +163,11 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetSupplyRecordForEditById(int supplyRecordId)
|
||||
{
|
||||
var result = _supplyRecordDataAccess.GetSupplyRecordById(supplyRecordId);
|
||||
if (result.RequisitionHistory.Any())
|
||||
{
|
||||
//requisition history when viewed through the supply is always immutable.
|
||||
result.RequisitionHistory = result.RequisitionHistory.Select(x => new SupplyUsageHistory { Id = default, Cost = x.Cost, Description = x.Description, Date = x.Date, PartNumber = x.PartNumber, Quantity = x.Quantity }).ToList();
|
||||
}
|
||||
//convert to Input object.
|
||||
var convertedResult = new SupplyRecordInput
|
||||
{
|
||||
@@ -180,14 +187,28 @@ namespace CarCareTracker.Controllers
|
||||
};
|
||||
return PartialView("_SupplyRecordModal", convertedResult);
|
||||
}
|
||||
private bool DeleteSupplyRecordWithChecks(int supplyRecordId)
|
||||
{
|
||||
var existingRecord = _supplyRecordDataAccess.GetSupplyRecordById(supplyRecordId);
|
||||
if (existingRecord.VehicleId != default)
|
||||
{
|
||||
//security check only if not editing shop supply.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(existingRecord.Id);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromSupplyRecord(existingRecord, "supplyrecord.delete", User.Identity.Name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DeleteSupplyRecordById(int supplyRecordId)
|
||||
{
|
||||
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(supplyRecordId);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Supply Record - Id: {supplyRecordId}");
|
||||
}
|
||||
var result = DeleteSupplyRecordWithChecks(supplyRecordId);
|
||||
return Json(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,49 +23,29 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
return PartialView("_TaxRecords", result);
|
||||
}
|
||||
private void UpdateRecurringTaxes(int vehicleId)
|
||||
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
public IActionResult CheckRecurringTaxRecords(int vehicleId)
|
||||
{
|
||||
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||
var recurringFees = result.Where(x => x.IsRecurring);
|
||||
if (recurringFees.Any())
|
||||
try
|
||||
{
|
||||
foreach (TaxRecord recurringFee in recurringFees)
|
||||
{
|
||||
var newDate = new DateTime();
|
||||
if (recurringFee.RecurringInterval != ReminderMonthInterval.Other)
|
||||
{
|
||||
newDate = recurringFee.Date.AddMonths((int)recurringFee.RecurringInterval);
|
||||
}
|
||||
else
|
||||
{
|
||||
newDate = recurringFee.Date.AddMonths(recurringFee.CustomMonthInterval);
|
||||
}
|
||||
if (DateTime.Now > newDate)
|
||||
{
|
||||
recurringFee.IsRecurring = false;
|
||||
var newRecurringFee = new TaxRecord()
|
||||
{
|
||||
VehicleId = recurringFee.VehicleId,
|
||||
Date = newDate,
|
||||
Description = recurringFee.Description,
|
||||
Cost = recurringFee.Cost,
|
||||
IsRecurring = true,
|
||||
Notes = recurringFee.Notes,
|
||||
RecurringInterval = recurringFee.RecurringInterval,
|
||||
CustomMonthInterval = recurringFee.CustomMonthInterval,
|
||||
Files = recurringFee.Files,
|
||||
Tags = recurringFee.Tags,
|
||||
ExtraFields = recurringFee.ExtraFields
|
||||
};
|
||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
|
||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(newRecurringFee);
|
||||
}
|
||||
}
|
||||
var result = _vehicleLogic.UpdateRecurringTaxes(vehicleId);
|
||||
return Json(result);
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return Json(false);
|
||||
}
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult SaveTaxRecordToVehicleId(TaxRecordInput taxRecord)
|
||||
{
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), taxRecord.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
//move files from temp.
|
||||
taxRecord.Files = taxRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||
//push back any reminders
|
||||
@@ -77,9 +57,10 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
}
|
||||
var result = _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord.ToTaxRecord());
|
||||
_vehicleLogic.UpdateRecurringTaxes(taxRecord.VehicleId);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), taxRecord.VehicleId, User.Identity.Name, $"{(taxRecord.Id == default ? "Created" : "Edited")} Tax Record - Description: {taxRecord.Description}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(taxRecord.ToTaxRecord(), taxRecord.Id == default ? "taxrecord.add" : "taxrecord.update", User.Identity.Name));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -92,6 +73,11 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetTaxRecordForEditById(int taxRecordId)
|
||||
{
|
||||
var result = _taxRecordDataAccess.GetTaxRecordById(taxRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||
{
|
||||
return Redirect("/Error/Unauthorized");
|
||||
}
|
||||
//convert to Input object.
|
||||
var convertedResult = new TaxRecordInput
|
||||
{
|
||||
@@ -104,20 +90,32 @@ namespace CarCareTracker.Controllers
|
||||
IsRecurring = result.IsRecurring,
|
||||
RecurringInterval = result.RecurringInterval,
|
||||
CustomMonthInterval = result.CustomMonthInterval,
|
||||
CustomMonthIntervalUnit = result.CustomMonthIntervalUnit,
|
||||
Files = result.Files,
|
||||
Tags = result.Tags,
|
||||
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields)
|
||||
};
|
||||
return PartialView("_TaxRecordModal", convertedResult);
|
||||
}
|
||||
private bool DeleteTaxRecordWithChecks(int taxRecordId)
|
||||
{
|
||||
var existingRecord = _taxRecordDataAccess.GetTaxRecordById(taxRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var result = _taxRecordDataAccess.DeleteTaxRecordById(existingRecord.Id);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(existingRecord, "taxrecord.delete", User.Identity.Name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DeleteTaxRecordById(int taxRecordId)
|
||||
{
|
||||
var result = _taxRecordDataAccess.DeleteTaxRecordById(taxRecordId);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Tax Record - Id: {taxRecordId}");
|
||||
}
|
||||
var result = DeleteTaxRecordWithChecks(taxRecordId);
|
||||
return Json(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult SaveUpgradeRecordToVehicleId(UpgradeRecordInput upgradeRecord)
|
||||
{
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), upgradeRecord.VehicleId))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
if (upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||
{
|
||||
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||
@@ -40,12 +45,16 @@ namespace CarCareTracker.Controllers
|
||||
upgradeRecord.Files = upgradeRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||
if (upgradeRecord.Supplies.Any())
|
||||
{
|
||||
upgradeRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(upgradeRecord.Supplies, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Description);
|
||||
upgradeRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(upgradeRecord.Supplies, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Description));
|
||||
if (upgradeRecord.CopySuppliesAttachment)
|
||||
{
|
||||
upgradeRecord.Files.AddRange(GetSuppliesAttachments(upgradeRecord.Supplies));
|
||||
}
|
||||
}
|
||||
if (upgradeRecord.DeletedRequisitionHistory.Any())
|
||||
{
|
||||
_vehicleLogic.RestoreSupplyRecordsByUsage(upgradeRecord.DeletedRequisitionHistory, upgradeRecord.Description);
|
||||
}
|
||||
//push back any reminders
|
||||
if (upgradeRecord.ReminderRecordId.Any())
|
||||
{
|
||||
@@ -57,7 +66,7 @@ namespace CarCareTracker.Controllers
|
||||
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), upgradeRecord.VehicleId, User.Identity.Name, $"{(upgradeRecord.Id == default ? "Created" : "Edited")} Upgrade Record - Description: {upgradeRecord.Description}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(upgradeRecord.ToUpgradeRecord(), upgradeRecord.Id == default ? "upgraderecord.add" : "upgraderecord.update", User.Identity.Name));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -70,6 +79,11 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetUpgradeRecordForEditById(int upgradeRecordId)
|
||||
{
|
||||
var result = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||
{
|
||||
return Redirect("/Error/Unauthorized");
|
||||
}
|
||||
//convert to Input object.
|
||||
var convertedResult = new UpgradeRecordInput
|
||||
{
|
||||
@@ -87,14 +101,30 @@ namespace CarCareTracker.Controllers
|
||||
};
|
||||
return PartialView("_UpgradeRecordModal", convertedResult);
|
||||
}
|
||||
private bool DeleteUpgradeRecordWithChecks(int upgradeRecordId)
|
||||
{
|
||||
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
|
||||
//security check.
|
||||
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
//restore any requisitioned supplies.
|
||||
if (existingRecord.RequisitionHistory.Any())
|
||||
{
|
||||
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
||||
}
|
||||
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(existingRecord.Id);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "upgraderecord.delete", User.Identity.Name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
|
||||
{
|
||||
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(upgradeRecordId);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Upgrade Record - Id: {upgradeRecordId}");
|
||||
}
|
||||
var result = DeleteUpgradeRecordWithChecks(upgradeRecordId);
|
||||
return Json(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using CarCareTracker.Helper;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Security.Claims;
|
||||
using CarCareTracker.Logic;
|
||||
using CarCareTracker.Filter;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Logic;
|
||||
using CarCareTracker.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Globalization;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.Controllers
|
||||
@@ -95,7 +95,6 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult Index(int vehicleId)
|
||||
{
|
||||
var data = _dataAccess.GetVehicleById(vehicleId);
|
||||
UpdateRecurringTaxes(vehicleId);
|
||||
return View(data);
|
||||
}
|
||||
[HttpGet]
|
||||
@@ -131,10 +130,11 @@ namespace CarCareTracker.Controllers
|
||||
if (isNewAddition)
|
||||
{
|
||||
_userLogic.AddUserAccessToVehicle(GetUserID(), vehicleInput.Id);
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleInput.Id, User.Identity.Name, $"Added Vehicle - Description: {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}");
|
||||
} else
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Created Vehicle {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}({StaticHelper.GetVehicleIdentifier(vehicleInput)})", "vehicle.add", User.Identity.Name, vehicleInput.Id.ToString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleInput.Id, User.Identity.Name, $"Edited Vehicle - Description: {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Updated Vehicle {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}({StaticHelper.GetVehicleIdentifier(vehicleInput)})", "vehicle.update", User.Identity.Name, vehicleInput.Id.ToString()));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -164,7 +164,7 @@ namespace CarCareTracker.Controllers
|
||||
_dataAccess.DeleteVehicle(vehicleId);
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, "Deleted Vehicle");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic(string.Empty, "vehicle.delete", User.Identity.Name, vehicleId.ToString()));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -188,18 +188,18 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "Both vehicles already have identical collaborators" });
|
||||
return Json(OperationResponse.Failed("Both vehicles already have identical collaborators"));
|
||||
}
|
||||
}
|
||||
return Json(new OperationResponse { Success = true, Message = "Collaborators Copied" });
|
||||
return Json(OperationResponse.Succeed("Collaborators Copied"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
return Json(OperationResponse.Failed());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region "Shared Methods"
|
||||
[HttpPost]
|
||||
public IActionResult GetFilesPendingUpload(List<UploadedFiles> uploadedFiles)
|
||||
@@ -216,7 +216,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
return Json(searchResults);
|
||||
}
|
||||
foreach(ImportMode visibleTab in _config.GetUserConfig(User).VisibleTabs)
|
||||
foreach (ImportMode visibleTab in _config.GetUserConfig(User).VisibleTabs)
|
||||
{
|
||||
switch (visibleTab)
|
||||
{
|
||||
@@ -389,7 +389,7 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Moved multiple {source.ToString()} to {destination.ToString()} - Ids: {string.Join(",", recordIds)}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Moved multiple {source.ToString()} to {destination.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.move", User.Identity.Name, string.Empty));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -401,37 +401,37 @@ namespace CarCareTracker.Controllers
|
||||
switch (importMode)
|
||||
{
|
||||
case ImportMode.ServiceRecord:
|
||||
result = _serviceRecordDataAccess.DeleteServiceRecordById(recordId);
|
||||
result = DeleteServiceRecordWithChecks(recordId);
|
||||
break;
|
||||
case ImportMode.RepairRecord:
|
||||
result = _collisionRecordDataAccess.DeleteCollisionRecordById(recordId);
|
||||
result = DeleteCollisionRecordWithChecks(recordId);
|
||||
break;
|
||||
case ImportMode.UpgradeRecord:
|
||||
result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(recordId);
|
||||
result = DeleteUpgradeRecordWithChecks(recordId);
|
||||
break;
|
||||
case ImportMode.GasRecord:
|
||||
result = _gasRecordDataAccess.DeleteGasRecordById(recordId);
|
||||
result = DeleteGasRecordWithChecks(recordId);
|
||||
break;
|
||||
case ImportMode.TaxRecord:
|
||||
result = _taxRecordDataAccess.DeleteTaxRecordById(recordId);
|
||||
result = DeleteTaxRecordWithChecks(recordId);
|
||||
break;
|
||||
case ImportMode.SupplyRecord:
|
||||
result = _supplyRecordDataAccess.DeleteSupplyRecordById(recordId);
|
||||
result = DeleteSupplyRecordWithChecks(recordId);
|
||||
break;
|
||||
case ImportMode.NoteRecord:
|
||||
result = _noteDataAccess.DeleteNoteById(recordId);
|
||||
result = DeleteNoteWithChecks(recordId);
|
||||
break;
|
||||
case ImportMode.OdometerRecord:
|
||||
result = _odometerRecordDataAccess.DeleteOdometerRecordById(recordId);
|
||||
result = DeleteOdometerRecordWithChecks(recordId);
|
||||
break;
|
||||
case ImportMode.ReminderRecord:
|
||||
result = _reminderRecordDataAccess.DeleteReminderRecordById(recordId);
|
||||
result = DeleteReminderRecordWithChecks(recordId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Deleted multiple {importMode.ToString()} - Ids: {string.Join(", ", recordIds)}", "bulk.delete", User.Identity.Name, string.Empty));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -490,10 +490,11 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Adjusted odometer for multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Adjusted odometer for multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.odometer.adjust", User.Identity.Name, string.Empty));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DuplicateRecords(List<int> recordIds, ImportMode importMode)
|
||||
{
|
||||
bool result = false;
|
||||
@@ -505,6 +506,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
|
||||
result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(existingRecord);
|
||||
}
|
||||
break;
|
||||
@@ -512,6 +514,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
|
||||
result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(existingRecord);
|
||||
}
|
||||
break;
|
||||
@@ -519,6 +522,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
|
||||
result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(existingRecord);
|
||||
}
|
||||
break;
|
||||
@@ -540,6 +544,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
var existingRecord = _supplyRecordDataAccess.GetSupplyRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
|
||||
result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(existingRecord);
|
||||
}
|
||||
break;
|
||||
@@ -564,11 +569,219 @@ namespace CarCareTracker.Controllers
|
||||
result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(existingRecord);
|
||||
}
|
||||
break;
|
||||
case ImportMode.PlanRecord:
|
||||
{
|
||||
var existingRecord = _planRecordDataAccess.GetPlanRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
existingRecord.ReminderRecordId = default;
|
||||
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
|
||||
result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}");
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.duplicate", User.Identity.Name, string.Empty));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DuplicateRecordsToOtherVehicles(List<int> recordIds, List<int> vehicleIds, ImportMode importMode)
|
||||
{
|
||||
bool result = false;
|
||||
if (!recordIds.Any() || !vehicleIds.Any())
|
||||
{
|
||||
return Json(result);
|
||||
}
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
switch (importMode)
|
||||
{
|
||||
case ImportMode.ServiceRecord:
|
||||
{
|
||||
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.RepairRecord:
|
||||
{
|
||||
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.UpgradeRecord:
|
||||
{
|
||||
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.GasRecord:
|
||||
{
|
||||
var existingRecord = _gasRecordDataAccess.GetGasRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _gasRecordDataAccess.SaveGasRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.TaxRecord:
|
||||
{
|
||||
var existingRecord = _taxRecordDataAccess.GetTaxRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _taxRecordDataAccess.SaveTaxRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.SupplyRecord:
|
||||
{
|
||||
var existingRecord = _supplyRecordDataAccess.GetSupplyRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.NoteRecord:
|
||||
{
|
||||
var existingRecord = _noteDataAccess.GetNoteById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _noteDataAccess.SaveNoteToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.OdometerRecord:
|
||||
{
|
||||
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.ReminderRecord:
|
||||
{
|
||||
var existingRecord = _reminderRecordDataAccess.GetReminderRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.PlanRecord:
|
||||
{
|
||||
var existingRecord = _planRecordDataAccess.GetPlanRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
existingRecord.ReminderRecordId = default;
|
||||
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)} - to Vehicle Ids: {string.Join(",", vehicleIds)}", "bulk.duplicate.to.vehicles", User.Identity.Name, string.Join(",", vehicleIds)));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult BulkCreateOdometerRecords(List<int> recordIds, ImportMode importMode)
|
||||
{
|
||||
bool result = false;
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
switch (importMode)
|
||||
{
|
||||
case ImportMode.ServiceRecord:
|
||||
{
|
||||
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(recordId);
|
||||
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||
{
|
||||
Date = existingRecord.Date,
|
||||
VehicleId = existingRecord.VehicleId,
|
||||
Mileage = existingRecord.Mileage,
|
||||
Notes = $"Auto Insert From Service Record: {existingRecord.Description}"
|
||||
});
|
||||
}
|
||||
break;
|
||||
case ImportMode.RepairRecord:
|
||||
{
|
||||
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(recordId);
|
||||
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||
{
|
||||
Date = existingRecord.Date,
|
||||
VehicleId = existingRecord.VehicleId,
|
||||
Mileage = existingRecord.Mileage,
|
||||
Notes = $"Auto Insert From Repair Record: {existingRecord.Description}"
|
||||
});
|
||||
}
|
||||
break;
|
||||
case ImportMode.UpgradeRecord:
|
||||
{
|
||||
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(recordId);
|
||||
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||
{
|
||||
Date = existingRecord.Date,
|
||||
VehicleId = existingRecord.VehicleId,
|
||||
Mileage = existingRecord.Mileage,
|
||||
Notes = $"Auto Insert From Upgrade Record: {existingRecord.Description}"
|
||||
});
|
||||
}
|
||||
break;
|
||||
case ImportMode.GasRecord:
|
||||
{
|
||||
var existingRecord = _gasRecordDataAccess.GetGasRecordById(recordId);
|
||||
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||
{
|
||||
Date = existingRecord.Date,
|
||||
VehicleId = existingRecord.VehicleId,
|
||||
Mileage = existingRecord.Mileage,
|
||||
Notes = $"Auto Insert From Gas Record. {existingRecord.Notes}"
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Created Odometer Records based on {importMode.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.odometer.insert", User.Identity.Name, string.Empty));
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
@@ -631,14 +844,15 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
if (extraFieldIsEdited)
|
||||
{
|
||||
foreach(ExtraField extraField in genericRecordEditModel.EditRecord.ExtraFields)
|
||||
foreach (ExtraField extraField in genericRecordEditModel.EditRecord.ExtraFields)
|
||||
{
|
||||
if (existingRecord.ExtraFields.Any(x=>x.Name == extraField.Name))
|
||||
if (existingRecord.ExtraFields.Any(x => x.Name == extraField.Name))
|
||||
{
|
||||
var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name);
|
||||
existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name);
|
||||
existingRecord.ExtraFields.Insert(insertIndex, extraField);
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
existingRecord.ExtraFields.Add(extraField);
|
||||
}
|
||||
@@ -744,6 +958,167 @@ namespace CarCareTracker.Controllers
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult PrintRecordStickers(int vehicleId, List<int> recordIds, ImportMode importMode)
|
||||
{
|
||||
bool result = false;
|
||||
if (!recordIds.Any())
|
||||
{
|
||||
return Json(result);
|
||||
}
|
||||
var stickerViewModel = new StickerViewModel() { RecordType = importMode };
|
||||
if (vehicleId != default)
|
||||
{
|
||||
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||
if (vehicleData != null && vehicleData.Id != default)
|
||||
{
|
||||
stickerViewModel.VehicleData = vehicleData;
|
||||
}
|
||||
}
|
||||
|
||||
int recordsAdded = 0;
|
||||
switch (importMode)
|
||||
{
|
||||
case ImportMode.ServiceRecord:
|
||||
{
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
stickerViewModel.GenericRecords.Add(_serviceRecordDataAccess.GetServiceRecordById(recordId));
|
||||
recordsAdded++;
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case ImportMode.RepairRecord:
|
||||
{
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
stickerViewModel.GenericRecords.Add(_collisionRecordDataAccess.GetCollisionRecordById(recordId));
|
||||
recordsAdded++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.UpgradeRecord:
|
||||
{
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
stickerViewModel.GenericRecords.Add(_upgradeRecordDataAccess.GetUpgradeRecordById(recordId));
|
||||
recordsAdded++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.GasRecord:
|
||||
{
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
var record = _gasRecordDataAccess.GetGasRecordById(recordId);
|
||||
stickerViewModel.GenericRecords.Add(new GenericRecord
|
||||
{
|
||||
Cost = record.Cost,
|
||||
Date = record.Date,
|
||||
Notes = record.Notes,
|
||||
Mileage = record.Mileage,
|
||||
ExtraFields = record.ExtraFields
|
||||
});
|
||||
recordsAdded++;
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case ImportMode.TaxRecord:
|
||||
{
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
var record = _taxRecordDataAccess.GetTaxRecordById(recordId);
|
||||
stickerViewModel.GenericRecords.Add(new GenericRecord
|
||||
{
|
||||
Description = record.Description,
|
||||
Cost = record.Cost,
|
||||
Notes = record.Notes,
|
||||
Date = record.Date,
|
||||
ExtraFields = record.ExtraFields
|
||||
});
|
||||
recordsAdded++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.SupplyRecord:
|
||||
{
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
var record = _supplyRecordDataAccess.GetSupplyRecordById(recordId);
|
||||
stickerViewModel.SupplyRecords.Add(record);
|
||||
recordsAdded++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.NoteRecord:
|
||||
{
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
var record = _noteDataAccess.GetNoteById(recordId);
|
||||
stickerViewModel.GenericRecords.Add(new GenericRecord
|
||||
{
|
||||
Description = record.Description,
|
||||
Notes = record.NoteText
|
||||
});
|
||||
recordsAdded++;
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case ImportMode.OdometerRecord:
|
||||
{
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
var record = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
|
||||
stickerViewModel.GenericRecords.Add(new GenericRecord
|
||||
{
|
||||
Date = record.Date,
|
||||
Mileage = record.Mileage,
|
||||
Notes = record.Notes,
|
||||
ExtraFields = record.ExtraFields
|
||||
});
|
||||
recordsAdded++;
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case ImportMode.ReminderRecord:
|
||||
{
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
stickerViewModel.ReminderRecords.Add(_reminderRecordDataAccess.GetReminderRecordById(recordId));
|
||||
recordsAdded++;
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case ImportMode.PlanRecord:
|
||||
{
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
var record = _planRecordDataAccess.GetPlanRecordById(recordId);
|
||||
stickerViewModel.GenericRecords.Add(new GenericRecord
|
||||
{
|
||||
Description = record.Description,
|
||||
Cost = record.Cost,
|
||||
Notes = record.Notes,
|
||||
Date = record.DateModified,
|
||||
ExtraFields = record.ExtraFields,
|
||||
RequisitionHistory = record.RequisitionHistory
|
||||
});
|
||||
recordsAdded++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (recordsAdded > 0)
|
||||
{
|
||||
return PartialView("_Stickers", stickerViewModel);
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult SaveUserColumnPreferences(UserColumnPreference columnPreference)
|
||||
{
|
||||
try
|
||||
|
||||
10
Enum/KioskMode.cs
Normal file
10
Enum/KioskMode.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public enum KioskMode
|
||||
{
|
||||
Vehicle = 0,
|
||||
Plan = 1,
|
||||
Reminder = 2,
|
||||
Cycle = 3
|
||||
}
|
||||
}
|
||||
8
Enum/ReminderIntervalUnit.cs
Normal file
8
Enum/ReminderIntervalUnit.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public enum ReminderIntervalUnit
|
||||
{
|
||||
Months = 1,
|
||||
Days = 2
|
||||
}
|
||||
}
|
||||
8
Enum/TagFilter.cs
Normal file
8
Enum/TagFilter.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public enum TagFilter
|
||||
{
|
||||
Exclude = 0,
|
||||
IncludeOnly = 1
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using LiteDB;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using LiteDB;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
@@ -19,27 +19,34 @@ namespace CarCareTracker.Filter
|
||||
{
|
||||
if (!filterContext.HttpContext.User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
var vehicleId = int.Parse(filterContext.ActionArguments["vehicleId"].ToString());
|
||||
if (vehicleId != default)
|
||||
if (filterContext.ActionArguments.ContainsKey("vehicleId"))
|
||||
{
|
||||
var userId = int.Parse(filterContext.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||
if (!_userLogic.UserCanEditVehicle(userId, vehicleId))
|
||||
var vehicleId = int.Parse(filterContext.ActionArguments["vehicleId"].ToString());
|
||||
if (vehicleId != default)
|
||||
{
|
||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||
var userId = int.Parse(filterContext.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||
if (!_userLogic.UserCanEditVehicle(userId, vehicleId))
|
||||
{
|
||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var shopSupplyEndpoints = new List<string> { "ImportToVehicleIdFromCsv", "GetSupplyRecordsByVehicleId", "ExportFromVehicleToCsv" };
|
||||
if (shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()) && !_config.GetServerEnableShopSupplies())
|
||||
{
|
||||
//user trying to access shop supplies but shop supplies is not enabled by root user.
|
||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||
}
|
||||
else if (!shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()))
|
||||
{
|
||||
//user trying to access any other endpoints using 0 as vehicle id.
|
||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
var shopSupplyEndpoints = new List<string> { "ImportToVehicleIdFromCsv", "GetSupplyRecordsByVehicleId", "ExportFromVehicleToCsv" };
|
||||
if (shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()) && !_config.GetServerEnableShopSupplies())
|
||||
{
|
||||
//user trying to access shop supplies but shop supplies is not enabled by root user.
|
||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||
}
|
||||
else if (!shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()))
|
||||
{
|
||||
//user trying to access any other endpoints using 0 as vehicle id.
|
||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||
}
|
||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using CarCareTracker.Models;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
{
|
||||
@@ -9,11 +10,13 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
OpenIDConfig GetOpenIDConfig();
|
||||
ReminderUrgencyConfig GetReminderUrgencyConfig();
|
||||
MailConfig GetMailConfig();
|
||||
UserConfig GetUserConfig(ClaimsPrincipal user);
|
||||
bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData);
|
||||
bool AuthenticateRootUser(string username, string password);
|
||||
bool AuthenticateRootUserOIDC(string email);
|
||||
string GetWebHookUrl();
|
||||
bool GetCustomWidgetsEnabled();
|
||||
string GetMOTD();
|
||||
string GetLogoUrl();
|
||||
string GetServerLanguage();
|
||||
@@ -21,39 +24,48 @@ namespace CarCareTracker.Helper
|
||||
bool GetServerEnableShopSupplies();
|
||||
string GetServerPostgresConnection();
|
||||
string GetAllowedFileUploadExtensions();
|
||||
public bool DeleteUserConfig(int userId);
|
||||
bool DeleteUserConfig(int userId);
|
||||
bool GetInvariantApi();
|
||||
bool GetServerOpenRegistration();
|
||||
}
|
||||
public class ConfigHelper : IConfigHelper
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
private readonly IUserConfigDataAccess _userConfig;
|
||||
private readonly ILogger<IConfigHelper> _logger;
|
||||
private IMemoryCache _cache;
|
||||
public ConfigHelper(IConfiguration serverConfig,
|
||||
IUserConfigDataAccess userConfig,
|
||||
IMemoryCache memoryCache)
|
||||
IMemoryCache memoryCache,
|
||||
ILogger<IConfigHelper> logger)
|
||||
{
|
||||
_config = serverConfig;
|
||||
_userConfig = userConfig;
|
||||
_cache = memoryCache;
|
||||
_logger = logger;
|
||||
}
|
||||
public string GetWebHookUrl()
|
||||
{
|
||||
var webhook = _config["LUBELOGGER_WEBHOOK"];
|
||||
if (string.IsNullOrWhiteSpace(webhook))
|
||||
{
|
||||
webhook = "";
|
||||
}
|
||||
var webhook = CheckString("LUBELOGGER_WEBHOOK");
|
||||
return webhook;
|
||||
}
|
||||
public bool GetCustomWidgetsEnabled()
|
||||
{
|
||||
return CheckBool(CheckString("LUBELOGGER_CUSTOM_WIDGETS"));
|
||||
}
|
||||
public bool GetInvariantApi()
|
||||
{
|
||||
return CheckBool(CheckString("LUBELOGGER_INVARIANT_API"));
|
||||
}
|
||||
public string GetMOTD()
|
||||
{
|
||||
var motd = _config["LUBELOGGER_MOTD"];
|
||||
if (string.IsNullOrWhiteSpace(motd))
|
||||
{
|
||||
motd = "";
|
||||
}
|
||||
var motd = CheckString("LUBELOGGER_MOTD");
|
||||
return motd;
|
||||
}
|
||||
public bool GetServerOpenRegistration()
|
||||
{
|
||||
return CheckBool(CheckString("LUBELOGGER_OPEN_REGISTRATION"));
|
||||
}
|
||||
public OpenIDConfig GetOpenIDConfig()
|
||||
{
|
||||
OpenIDConfig openIdConfig = _config.GetSection("OpenIDConfig").Get<OpenIDConfig>() ?? new OpenIDConfig();
|
||||
@@ -64,27 +76,25 @@ namespace CarCareTracker.Helper
|
||||
ReminderUrgencyConfig reminderUrgencyConfig = _config.GetSection("ReminderUrgencyConfig").Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig();
|
||||
return reminderUrgencyConfig;
|
||||
}
|
||||
public MailConfig GetMailConfig()
|
||||
{
|
||||
MailConfig mailConfig = _config.GetSection("MailConfig").Get<MailConfig>() ?? new MailConfig();
|
||||
return mailConfig;
|
||||
}
|
||||
public string GetLogoUrl()
|
||||
{
|
||||
var logoUrl = _config["LUBELOGGER_LOGO_URL"];
|
||||
if (string.IsNullOrWhiteSpace(logoUrl))
|
||||
{
|
||||
logoUrl = "/defaults/lubelogger_logo.png";
|
||||
}
|
||||
var logoUrl = CheckString("LUBELOGGER_LOGO_URL", "/defaults/lubelogger_logo.png");
|
||||
return logoUrl;
|
||||
}
|
||||
public string GetAllowedFileUploadExtensions()
|
||||
{
|
||||
var allowedFileExtensions = _config["LUBELOGGER_ALLOWED_FILE_EXTENSIONS"];
|
||||
if (string.IsNullOrWhiteSpace(allowedFileExtensions)){
|
||||
return StaticHelper.DefaultAllowedFileExtensions;
|
||||
}
|
||||
var allowedFileExtensions = CheckString("LUBELOGGER_ALLOWED_FILE_EXTENSIONS", StaticHelper.DefaultAllowedFileExtensions);
|
||||
return allowedFileExtensions;
|
||||
}
|
||||
public bool AuthenticateRootUser(string username, string password)
|
||||
{
|
||||
var rootUsername = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty;
|
||||
var rootPassword = _config[nameof(UserConfig.UserPasswordHash)] ?? string.Empty;
|
||||
var rootUsername = CheckString(nameof(UserConfig.UserNameHash));
|
||||
var rootPassword = CheckString(nameof(UserConfig.UserPasswordHash));
|
||||
if (string.IsNullOrWhiteSpace(rootUsername) || string.IsNullOrWhiteSpace(rootPassword))
|
||||
{
|
||||
return false;
|
||||
@@ -93,8 +103,8 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
public bool AuthenticateRootUserOIDC(string email)
|
||||
{
|
||||
var rootEmail = _config[nameof(UserConfig.DefaultReminderEmail)] ?? string.Empty;
|
||||
var rootUserOIDC = bool.Parse(_config[nameof(UserConfig.EnableRootUserOIDC)]);
|
||||
var rootEmail = CheckString(nameof(UserConfig.DefaultReminderEmail));
|
||||
var rootUserOIDC = CheckBool(CheckString(nameof(UserConfig.EnableRootUserOIDC)));
|
||||
if (!rootUserOIDC || string.IsNullOrWhiteSpace(rootEmail))
|
||||
{
|
||||
return false;
|
||||
@@ -103,27 +113,22 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
public string GetServerLanguage()
|
||||
{
|
||||
var serverLanguage = _config[nameof(UserConfig.UserLanguage)] ?? "en_US";
|
||||
var serverLanguage = CheckString(nameof(UserConfig.UserLanguage), "en_US");
|
||||
return serverLanguage;
|
||||
}
|
||||
public bool GetServerDisabledRegistration()
|
||||
{
|
||||
var registrationDisabled = bool.Parse(_config[nameof(UserConfig.DisableRegistration)]);
|
||||
var registrationDisabled = CheckBool(CheckString(nameof(UserConfig.DisableRegistration)));
|
||||
return registrationDisabled;
|
||||
}
|
||||
public string GetServerPostgresConnection()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_config["POSTGRES_CONNECTION"]))
|
||||
{
|
||||
return _config["POSTGRES_CONNECTION"];
|
||||
} else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
var postgresConnection = CheckString("POSTGRES_CONNECTION");
|
||||
return postgresConnection;
|
||||
}
|
||||
public bool GetServerEnableShopSupplies()
|
||||
{
|
||||
return bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)] ?? "false");
|
||||
return CheckBool(CheckString(nameof(UserConfig.EnableShopSupplies)));
|
||||
}
|
||||
public bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData)
|
||||
{
|
||||
@@ -141,18 +146,19 @@ namespace CarCareTracker.Helper
|
||||
if (!File.Exists(StaticHelper.UserConfigPath))
|
||||
{
|
||||
//if file doesn't exist it might be because it's running on a mounted volume in docker.
|
||||
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(new UserConfig()));
|
||||
File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(new UserConfig()));
|
||||
}
|
||||
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
|
||||
configData.EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)] ?? "false");
|
||||
configData.UserNameHash = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty;
|
||||
configData.UserPasswordHash = _config[nameof(UserConfig.UserPasswordHash)] ?? string.Empty;
|
||||
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(configData));
|
||||
File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(configData));
|
||||
_cache.Set<UserConfig>($"userConfig_{userId}", configData);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex.Message);
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
@@ -173,36 +179,73 @@ namespace CarCareTracker.Helper
|
||||
var result = _userConfig.DeleteUserConfig(userId);
|
||||
return result;
|
||||
}
|
||||
private bool CheckBool(string value, bool defaultValue = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
else if (bool.TryParse(value, out bool result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"ConfigHelper Warning: You might be missing keys in appsettings.json, Message: ${ex.Message}");
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
private string CheckString(string configName, string defaultValue = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
var configValue = _config[configName] ?? defaultValue;
|
||||
return configValue;
|
||||
} catch(Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"ConfigHelper Warning: You might be missing keys in appsettings.json, Message: ${ex.Message}");
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
public UserConfig GetUserConfig(ClaimsPrincipal user)
|
||||
{
|
||||
var serverConfig = new UserConfig
|
||||
{
|
||||
EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]),
|
||||
UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]),
|
||||
UseSystemColorMode = bool.Parse(_config[nameof(UserConfig.UseSystemColorMode)]),
|
||||
UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]),
|
||||
UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]),
|
||||
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
|
||||
EnableRootUserOIDC = bool.Parse(_config[nameof(UserConfig.EnableRootUserOIDC)]),
|
||||
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
|
||||
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]),
|
||||
UseMarkDownOnSavedNotes = bool.Parse(_config[nameof(UserConfig.UseMarkDownOnSavedNotes)]),
|
||||
UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)]),
|
||||
EnableAutoReminderRefresh = bool.Parse(_config[nameof(UserConfig.EnableAutoReminderRefresh)]),
|
||||
EnableAutoOdometerInsert = bool.Parse(_config[nameof(UserConfig.EnableAutoOdometerInsert)]),
|
||||
PreferredGasMileageUnit = _config[nameof(UserConfig.PreferredGasMileageUnit)],
|
||||
PreferredGasUnit = _config[nameof(UserConfig.PreferredGasUnit)],
|
||||
UserLanguage = _config[nameof(UserConfig.UserLanguage)],
|
||||
HideSoldVehicles = bool.Parse(_config[nameof(UserConfig.HideSoldVehicles)]),
|
||||
EnableShopSupplies = bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)]),
|
||||
EnableExtraFieldColumns = bool.Parse(_config[nameof(UserConfig.EnableExtraFieldColumns)]),
|
||||
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>(),
|
||||
TabOrder = _config.GetSection(nameof(UserConfig.TabOrder)).Get<List<ImportMode>>(),
|
||||
EnableCsvImports = CheckBool(CheckString(nameof(UserConfig.EnableCsvImports)), true),
|
||||
UseDarkMode = CheckBool(CheckString(nameof(UserConfig.UseDarkMode))),
|
||||
UseSystemColorMode = CheckBool(CheckString(nameof(UserConfig.UseSystemColorMode))),
|
||||
UseMPG = CheckBool(CheckString(nameof(UserConfig.UseMPG)), true),
|
||||
UseDescending = CheckBool(CheckString(nameof(UserConfig.UseDescending))),
|
||||
EnableAuth = CheckBool(CheckString(nameof(UserConfig.EnableAuth))),
|
||||
EnableRootUserOIDC = CheckBool(CheckString(nameof(UserConfig.EnableRootUserOIDC))),
|
||||
HideZero = CheckBool(CheckString(nameof(UserConfig.HideZero))),
|
||||
AutomaticDecimalFormat = CheckBool(CheckString(nameof(UserConfig.AutomaticDecimalFormat))),
|
||||
UseUKMPG = CheckBool(CheckString(nameof(UserConfig.UseUKMPG))),
|
||||
UseMarkDownOnSavedNotes = CheckBool(CheckString(nameof(UserConfig.UseMarkDownOnSavedNotes))),
|
||||
UseThreeDecimalGasCost = CheckBool(CheckString(nameof(UserConfig.UseThreeDecimalGasCost)), true),
|
||||
UseThreeDecimalGasConsumption = CheckBool(CheckString(nameof(UserConfig.UseThreeDecimalGasConsumption)), true),
|
||||
EnableAutoReminderRefresh = CheckBool(CheckString(nameof(UserConfig.EnableAutoReminderRefresh))),
|
||||
EnableAutoOdometerInsert = CheckBool(CheckString(nameof(UserConfig.EnableAutoOdometerInsert))),
|
||||
PreferredGasMileageUnit = CheckString(nameof(UserConfig.PreferredGasMileageUnit)),
|
||||
PreferredGasUnit = CheckString(nameof(UserConfig.PreferredGasUnit)),
|
||||
UseUnitForFuelCost = CheckBool(CheckString(nameof(UserConfig.UseUnitForFuelCost))),
|
||||
UserLanguage = CheckString(nameof(UserConfig.UserLanguage), "en_US"),
|
||||
HideSoldVehicles = CheckBool(CheckString(nameof(UserConfig.HideSoldVehicles))),
|
||||
EnableShopSupplies = CheckBool(CheckString(nameof(UserConfig.EnableShopSupplies))),
|
||||
EnableExtraFieldColumns = CheckBool(CheckString(nameof(UserConfig.EnableExtraFieldColumns))),
|
||||
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>() ?? new UserConfig().VisibleTabs,
|
||||
TabOrder = _config.GetSection(nameof(UserConfig.TabOrder)).Get<List<ImportMode>>() ?? new UserConfig().TabOrder,
|
||||
UserColumnPreferences = _config.GetSection(nameof(UserConfig.UserColumnPreferences)).Get<List<UserColumnPreference>>() ?? new List<UserColumnPreference>(),
|
||||
ReminderUrgencyConfig = _config.GetSection(nameof(UserConfig.ReminderUrgencyConfig)).Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig(),
|
||||
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)]),
|
||||
DefaultReminderEmail = _config[nameof(UserConfig.DefaultReminderEmail)],
|
||||
DisableRegistration = bool.Parse(_config[nameof(UserConfig.DisableRegistration)])
|
||||
DefaultTab = (ImportMode)int.Parse(CheckString(nameof(UserConfig.DefaultTab), "8")),
|
||||
DefaultReminderEmail = CheckString(nameof(UserConfig.DefaultReminderEmail)),
|
||||
DisableRegistration = CheckBool(CheckString(nameof(UserConfig.DisableRegistration)))
|
||||
};
|
||||
int userId = 0;
|
||||
if (user != null)
|
||||
|
||||
@@ -16,6 +16,10 @@ namespace CarCareTracker.Helper
|
||||
int ClearTempFolder();
|
||||
int ClearUnlinkedThumbnails(List<string> linkedImages);
|
||||
int ClearUnlinkedDocuments(List<string> linkedDocuments);
|
||||
string GetWidgets();
|
||||
bool WidgetsExist();
|
||||
bool SaveWidgets(string widgetsData);
|
||||
bool DeleteWidgets();
|
||||
}
|
||||
public class FileHelper : IFileHelper
|
||||
{
|
||||
@@ -30,7 +34,7 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
public List<string> GetLanguages()
|
||||
{
|
||||
var languagePath = Path.Combine(_webEnv.WebRootPath, "translations");
|
||||
var languagePath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
|
||||
var defaultList = new List<string>() { "en_US" };
|
||||
if (Directory.Exists(languagePath))
|
||||
{
|
||||
@@ -68,7 +72,7 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
currentFilePath = currentFilePath.Substring(1);
|
||||
}
|
||||
string oldFilePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
|
||||
string oldFilePath = currentFilePath.StartsWith("defaults/") ? Path.Combine(_webEnv.WebRootPath, currentFilePath) : Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
|
||||
if (File.Exists(oldFilePath))
|
||||
{
|
||||
return oldFilePath;
|
||||
@@ -90,7 +94,7 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
try
|
||||
{
|
||||
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{Guid.NewGuid()}");
|
||||
var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{Guid.NewGuid()}");
|
||||
if (!Directory.Exists(tempPath))
|
||||
Directory.CreateDirectory(tempPath);
|
||||
//extract zip file
|
||||
@@ -100,10 +104,11 @@ namespace CarCareTracker.Helper
|
||||
var documentPath = Path.Combine(tempPath, "documents");
|
||||
var translationPath = Path.Combine(tempPath, "translations");
|
||||
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
|
||||
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
|
||||
var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath);
|
||||
var configPath = Path.Combine(tempPath, StaticHelper.LegacyUserConfigPath);
|
||||
if (Directory.Exists(imagePath))
|
||||
{
|
||||
var existingPath = Path.Combine(_webEnv.WebRootPath, "images");
|
||||
var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "images");
|
||||
if (!Directory.Exists(existingPath))
|
||||
{
|
||||
Directory.CreateDirectory(existingPath);
|
||||
@@ -125,7 +130,7 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
if (Directory.Exists(documentPath))
|
||||
{
|
||||
var existingPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
||||
var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "documents");
|
||||
if (!Directory.Exists(existingPath))
|
||||
{
|
||||
Directory.CreateDirectory(existingPath);
|
||||
@@ -147,7 +152,7 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
if (Directory.Exists(translationPath))
|
||||
{
|
||||
var existingPath = Path.Combine(_webEnv.WebRootPath, "translations");
|
||||
var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
|
||||
if (!Directory.Exists(existingPath))
|
||||
{
|
||||
Directory.CreateDirectory(existingPath);
|
||||
@@ -174,12 +179,16 @@ namespace CarCareTracker.Helper
|
||||
//data path will always exist as it is created on startup if not.
|
||||
File.Move(dataPath, StaticHelper.DbName, true);
|
||||
}
|
||||
if (File.Exists(widgetPath))
|
||||
{
|
||||
File.Move(widgetPath, StaticHelper.AdditionalWidgetsPath, true);
|
||||
}
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
//check if config folder exists.
|
||||
if (!Directory.Exists("config/"))
|
||||
if (!Directory.Exists("data/config"))
|
||||
{
|
||||
Directory.CreateDirectory("config/");
|
||||
Directory.CreateDirectory("data/config");
|
||||
}
|
||||
File.Move(configPath, StaticHelper.UserConfigPath, true);
|
||||
}
|
||||
@@ -194,7 +203,7 @@ namespace CarCareTracker.Helper
|
||||
public string MakeAttachmentsExport(List<GenericReportModel> exportData)
|
||||
{
|
||||
var folderName = Guid.NewGuid();
|
||||
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
|
||||
var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{folderName}");
|
||||
if (!Directory.Exists(tempPath))
|
||||
Directory.CreateDirectory(tempPath);
|
||||
int fileIndex = 0;
|
||||
@@ -218,11 +227,12 @@ namespace CarCareTracker.Helper
|
||||
public string MakeBackup()
|
||||
{
|
||||
var folderName = $"db_backup_{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}";
|
||||
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
|
||||
var imagePath = Path.Combine(_webEnv.WebRootPath, "images");
|
||||
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
||||
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations");
|
||||
var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{folderName}");
|
||||
var imagePath = Path.Combine(_webEnv.ContentRootPath, "data", "images");
|
||||
var documentPath = Path.Combine(_webEnv.ContentRootPath, "data", "documents");
|
||||
var translationPath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
|
||||
var dataPath = StaticHelper.DbName;
|
||||
var widgetPath = StaticHelper.AdditionalWidgetsPath;
|
||||
var configPath = StaticHelper.UserConfigPath;
|
||||
if (!Directory.Exists(tempPath))
|
||||
Directory.CreateDirectory(tempPath);
|
||||
@@ -262,6 +272,12 @@ namespace CarCareTracker.Helper
|
||||
Directory.CreateDirectory(newPath);
|
||||
File.Copy(dataPath, $"{newPath}/{Path.GetFileName(dataPath)}");
|
||||
}
|
||||
if (File.Exists(widgetPath))
|
||||
{
|
||||
var newPath = Path.Combine(tempPath, "data");
|
||||
Directory.CreateDirectory(newPath);
|
||||
File.Copy(widgetPath, $"{newPath}/{Path.GetFileName(widgetPath)}");
|
||||
}
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
var newPath = Path.Combine(tempPath, "config");
|
||||
@@ -285,8 +301,8 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
currentFilePath = currentFilePath.Substring(1);
|
||||
}
|
||||
string uploadPath = Path.Combine(_webEnv.WebRootPath, newFolder);
|
||||
string oldFilePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
|
||||
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", newFolder);
|
||||
string oldFilePath = Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
|
||||
if (!Directory.Exists(uploadPath))
|
||||
Directory.CreateDirectory(uploadPath);
|
||||
string newFileUploadPath = oldFilePath.Replace(tempPath, newFolder);
|
||||
@@ -303,7 +319,7 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
currentFilePath = currentFilePath.Substring(1);
|
||||
}
|
||||
string filePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
|
||||
string filePath = Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
@@ -323,12 +339,20 @@ namespace CarCareTracker.Helper
|
||||
var tempPath = GetFullFilePath("temp", false);
|
||||
if (Directory.Exists(tempPath))
|
||||
{
|
||||
//delete files
|
||||
var files = Directory.GetFiles(tempPath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
File.Delete(file);
|
||||
filesDeleted++;
|
||||
}
|
||||
//delete folders
|
||||
var folders = Directory.GetDirectories(tempPath);
|
||||
foreach(var folder in folders)
|
||||
{
|
||||
Directory.Delete(folder, true);
|
||||
filesDeleted++;
|
||||
}
|
||||
}
|
||||
return filesDeleted;
|
||||
}
|
||||
@@ -368,5 +392,57 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
return filesDeleted;
|
||||
}
|
||||
public string GetWidgets()
|
||||
{
|
||||
if (File.Exists(StaticHelper.AdditionalWidgetsPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
//read file
|
||||
var widgets = File.ReadAllText(StaticHelper.AdditionalWidgetsPath);
|
||||
return widgets;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
public bool WidgetsExist()
|
||||
{
|
||||
return File.Exists(StaticHelper.AdditionalWidgetsPath);
|
||||
}
|
||||
public bool SaveWidgets(string widgetsData)
|
||||
{
|
||||
try
|
||||
{
|
||||
//Delete Widgets if exists
|
||||
DeleteWidgets();
|
||||
File.WriteAllText(StaticHelper.AdditionalWidgetsPath, widgetsData);
|
||||
return true;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteWidgets()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(StaticHelper.AdditionalWidgetsPath))
|
||||
{
|
||||
File.Delete(StaticHelper.AdditionalWidgetsPath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,8 @@ namespace CarCareTracker.Helper
|
||||
MissedFuelUp = currentObject.MissedFuelUp,
|
||||
Notes = currentObject.Notes,
|
||||
Tags = currentObject.Tags,
|
||||
ExtraFields = currentObject.ExtraFields
|
||||
ExtraFields = currentObject.ExtraFields,
|
||||
Files = currentObject.Files
|
||||
};
|
||||
if (currentObject.MissedFuelUp)
|
||||
{
|
||||
@@ -86,9 +87,9 @@ namespace CarCareTracker.Helper
|
||||
unFactoredConsumption = 0;
|
||||
unFactoredMileage = 0;
|
||||
}
|
||||
else if (currentObject.IsFillToFull)
|
||||
else if (currentObject.IsFillToFull && currentObject.Mileage != default)
|
||||
{
|
||||
//if user filled to full.
|
||||
//if user filled to full and an odometer is provided, otherwise we will defer calculations
|
||||
if (convertedConsumption > 0.00M && deltaMileage > 0)
|
||||
{
|
||||
try
|
||||
@@ -130,10 +131,14 @@ namespace CarCareTracker.Helper
|
||||
MissedFuelUp = currentObject.MissedFuelUp,
|
||||
Notes = currentObject.Notes,
|
||||
Tags = currentObject.Tags,
|
||||
ExtraFields = currentObject.ExtraFields
|
||||
ExtraFields = currentObject.ExtraFields,
|
||||
Files = currentObject.Files
|
||||
});
|
||||
}
|
||||
previousMileage = currentObject.Mileage;
|
||||
if (currentObject.Mileage != default)
|
||||
{
|
||||
previousMileage = currentObject.Mileage;
|
||||
}
|
||||
}
|
||||
return computedResults;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using CarCareTracker.Models;
|
||||
using MimeKit;
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
{
|
||||
@@ -14,122 +15,128 @@ namespace CarCareTracker.Helper
|
||||
public class MailHelper : IMailHelper
|
||||
{
|
||||
private readonly MailConfig mailConfig;
|
||||
private readonly string serverLanguage;
|
||||
private readonly IFileHelper _fileHelper;
|
||||
private readonly ITranslationHelper _translator;
|
||||
private readonly ILogger<MailHelper> _logger;
|
||||
public MailHelper(
|
||||
IConfiguration config,
|
||||
IConfigHelper config,
|
||||
IFileHelper fileHelper,
|
||||
ITranslationHelper translationHelper,
|
||||
ILogger<MailHelper> logger
|
||||
) {
|
||||
//load mailConfig from Configuration
|
||||
mailConfig = config.GetSection("MailConfig").Get<MailConfig>() ?? new MailConfig();
|
||||
mailConfig = config.GetMailConfig();
|
||||
serverLanguage = config.GetServerLanguage();
|
||||
_fileHelper = fileHelper;
|
||||
_translator = translationHelper;
|
||||
_logger = logger;
|
||||
}
|
||||
public OperationResponse NotifyUserForRegistration(string emailAddress, string token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
|
||||
return OperationResponse.Failed("SMTP Server Not Setup");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) {
|
||||
return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" };
|
||||
return OperationResponse.Failed("Email Address or Token is invalid");
|
||||
}
|
||||
string emailSubject = "Your Registration Token for LubeLogger";
|
||||
string emailBody = $"A token has been generated on your behalf, please complete your registration for LubeLogger using the token: {token}";
|
||||
string emailSubject = _translator.Translate(serverLanguage, "Your Registration Token for LubeLogger");
|
||||
string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please complete your registration for LubeLogger using the token")}: {token}";
|
||||
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
|
||||
if (result)
|
||||
{
|
||||
return new OperationResponse { Success = true, Message = "Email Sent!" };
|
||||
return OperationResponse.Succeed("Email Sent!");
|
||||
} else
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||
return OperationResponse.Failed();
|
||||
}
|
||||
}
|
||||
public OperationResponse NotifyUserForPasswordReset(string emailAddress, string token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
|
||||
return OperationResponse.Failed("SMTP Server Not Setup");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" };
|
||||
return OperationResponse.Failed("Email Address or Token is invalid");
|
||||
}
|
||||
string emailSubject = "Your Password Reset Token for LubeLogger";
|
||||
string emailBody = $"A token has been generated on your behalf, please reset your password for LubeLogger using the token: {token}";
|
||||
string emailSubject = _translator.Translate(serverLanguage, "Your Password Reset Token for LubeLogger");
|
||||
string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please reset your password for LubeLogger using the token")}: {token}";
|
||||
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
|
||||
if (result)
|
||||
{
|
||||
return new OperationResponse { Success = true, Message = "Email Sent!" };
|
||||
return OperationResponse.Succeed("Email Sent!");
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||
return OperationResponse.Failed();
|
||||
}
|
||||
}
|
||||
public OperationResponse NotifyUserForAccountUpdate(string emailAddress, string token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
|
||||
return OperationResponse.Failed("SMTP Server Not Setup");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" };
|
||||
return OperationResponse.Failed("Email Address or Token is invalid");
|
||||
}
|
||||
string emailSubject = "Your User Account Update Token for LubeLogger";
|
||||
string emailBody = $"A token has been generated on your behalf, please update your account for LubeLogger using the token: {token}";
|
||||
string emailSubject = _translator.Translate(serverLanguage, "Your User Account Update Token for LubeLogger");
|
||||
string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please update your account for LubeLogger using the token")}: {token}";
|
||||
var result = SendEmail(new List<string> { emailAddress}, emailSubject, emailBody);
|
||||
if (result)
|
||||
{
|
||||
return new OperationResponse { Success = true, Message = "Email Sent!" };
|
||||
return OperationResponse.Succeed("Email Sent!");
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||
return OperationResponse.Failed();
|
||||
}
|
||||
}
|
||||
public OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
|
||||
return OperationResponse.Failed("SMTP Server Not Setup");
|
||||
}
|
||||
if (!emailAddresses.Any())
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "No recipients could be found" };
|
||||
return OperationResponse.Failed("No recipients could be found");
|
||||
}
|
||||
if (!reminders.Any())
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "No reminders could be found" };
|
||||
return OperationResponse.Failed("No reminders could be found");
|
||||
}
|
||||
//get email template, this file has to exist since it's a static file.
|
||||
var emailTemplatePath = _fileHelper.GetFullFilePath(StaticHelper.ReminderEmailTemplate);
|
||||
string emailSubject = $"Vehicle Reminders From LubeLogger - {DateTime.Now.ToShortDateString()}";
|
||||
string emailSubject = $"{_translator.Translate(serverLanguage, "Vehicle Reminders From LubeLogger")} - {DateTime.Now.ToShortDateString()}";
|
||||
//construct html table.
|
||||
string emailBody = File.ReadAllText(emailTemplatePath);
|
||||
emailBody = emailBody.Replace("{VehicleInformation}", $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)}");
|
||||
string tableHeader = $"<th>{_translator.Translate(serverLanguage, "Urgency")}</th><th>{_translator.Translate(serverLanguage, "Description")}</th><th>{_translator.Translate(serverLanguage, "Due")}</th>";
|
||||
string tableBody = "";
|
||||
foreach(ReminderRecordViewModel reminder in reminders)
|
||||
{
|
||||
var dueOn = reminder.Metric == ReminderMetric.Both ? $"{reminder.Date.ToShortDateString()} or {reminder.Mileage}" : reminder.Metric == ReminderMetric.Date ? $"{reminder.Date.ToShortDateString()}" : $"{reminder.Mileage}";
|
||||
tableBody += $"<tr class='{reminder.Urgency}'><td>{StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency)}</td><td>{reminder.Description}</td><td>{dueOn}</td></tr>";
|
||||
tableBody += $"<tr class='{reminder.Urgency}'><td>{_translator.Translate(serverLanguage, StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency))}</td><td>{reminder.Description}</td><td>{dueOn}</td></tr>";
|
||||
}
|
||||
emailBody = emailBody.Replace("{TableBody}", tableBody);
|
||||
emailBody = emailBody.Replace("{TableHeader}", tableHeader).Replace("{TableBody}", tableBody);
|
||||
try
|
||||
{
|
||||
var result = SendEmail(emailAddresses, emailSubject, emailBody);
|
||||
if (result)
|
||||
{
|
||||
return new OperationResponse { Success = true, Message = "Email Sent!" };
|
||||
return OperationResponse.Succeed("Email Sent!");
|
||||
} else
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||
return OperationResponse.Failed();
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = ex.Message };
|
||||
return OperationResponse.Failed(ex.Message);
|
||||
}
|
||||
}
|
||||
private bool SendEmail(List<string> emailTo, string emailSubject, string emailBody) {
|
||||
@@ -151,7 +158,7 @@ namespace CarCareTracker.Helper
|
||||
|
||||
using (var client = new SmtpClient())
|
||||
{
|
||||
client.Connect(server, mailConfig.Port, MailKit.Security.SecureSocketOptions.Auto);
|
||||
client.Connect(server, mailConfig.Port, SecureSocketOptions.Auto);
|
||||
//perform authentication if either username or password is provided.
|
||||
//do not perform authentication if neither are provided.
|
||||
if (!string.IsNullOrWhiteSpace(mailConfig.Username) || !string.IsNullOrWhiteSpace(mailConfig.Password)) {
|
||||
|
||||
@@ -25,7 +25,14 @@ namespace CarCareTracker.Helper
|
||||
existingReminder.Date = newDate.AddMonths((int)existingReminder.ReminderMonthInterval);
|
||||
} else
|
||||
{
|
||||
existingReminder.Date = newDate.Date.AddMonths(existingReminder.CustomMonthInterval);
|
||||
if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Months)
|
||||
{
|
||||
existingReminder.Date = newDate.Date.AddMonths(existingReminder.CustomMonthInterval);
|
||||
}
|
||||
else if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Days)
|
||||
{
|
||||
existingReminder.Date = newDate.Date.AddDays(existingReminder.CustomMonthInterval);
|
||||
}
|
||||
}
|
||||
|
||||
if (existingReminder.ReminderMileageInterval != ReminderMileageInterval.Other)
|
||||
@@ -55,7 +62,14 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
else
|
||||
{
|
||||
existingReminder.Date = newDate.AddMonths(existingReminder.CustomMonthInterval);
|
||||
if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Months)
|
||||
{
|
||||
existingReminder.Date = newDate.AddMonths(existingReminder.CustomMonthInterval);
|
||||
}
|
||||
else if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Days)
|
||||
{
|
||||
existingReminder.Date = newDate.AddDays(existingReminder.CustomMonthInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
return existingReminder;
|
||||
@@ -79,6 +93,7 @@ namespace CarCareTracker.Helper
|
||||
Description = reminder.Description,
|
||||
Notes = reminder.Notes,
|
||||
Metric = reminder.Metric,
|
||||
UserMetric = reminder.Metric,
|
||||
IsRecurring = reminder.IsRecurring,
|
||||
Tags = reminder.Tags
|
||||
};
|
||||
|
||||
@@ -5,93 +5,166 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
public interface IReportHelper
|
||||
{
|
||||
IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0);
|
||||
IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0);
|
||||
IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0);
|
||||
IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0);
|
||||
IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0);
|
||||
IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0);
|
||||
IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0, bool sortIntoYear = false);
|
||||
IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0, bool sortIntoYear = false);
|
||||
IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0, bool sortIntoYear = false);
|
||||
IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0, bool sortIntoYear = false);
|
||||
IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0, bool sortIntoYear = false);
|
||||
IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0, bool sortIntoYear = false);
|
||||
}
|
||||
public class ReportHelper: IReportHelper
|
||||
{
|
||||
public IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0)
|
||||
public IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0, bool sortIntoYear = false)
|
||||
{
|
||||
if (year != default)
|
||||
{
|
||||
odometerRecords.RemoveAll(x => x.Date.Year != year);
|
||||
}
|
||||
return odometerRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
if (sortIntoYear)
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = 0,
|
||||
DistanceTraveled = x.Sum(y=>y.DistanceTraveled)
|
||||
});
|
||||
return odometerRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key.Month,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
|
||||
Year = x.Key.Year,
|
||||
Cost = 0,
|
||||
DistanceTraveled = x.Sum(y => y.DistanceTraveled)
|
||||
});
|
||||
} else
|
||||
{
|
||||
return odometerRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = 0,
|
||||
DistanceTraveled = x.Sum(y => y.DistanceTraveled)
|
||||
});
|
||||
}
|
||||
}
|
||||
public IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0)
|
||||
public IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0, bool sortIntoYear = false)
|
||||
{
|
||||
if (year != default)
|
||||
{
|
||||
serviceRecords.RemoveAll(x => x.Date.Year != year);
|
||||
}
|
||||
return serviceRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
if (sortIntoYear)
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
return serviceRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key.Month,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
|
||||
Year = x.Key.Year,
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
} else
|
||||
{
|
||||
return serviceRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
}
|
||||
}
|
||||
public IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0)
|
||||
public IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0, bool sortIntoYear = false)
|
||||
{
|
||||
if (year != default)
|
||||
{
|
||||
repairRecords.RemoveAll(x => x.Date.Year != year);
|
||||
}
|
||||
return repairRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
if (sortIntoYear)
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
return repairRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key.Month,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
|
||||
Year = x.Key.Year,
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
} else
|
||||
{
|
||||
return repairRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
}
|
||||
}
|
||||
public IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0)
|
||||
public IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0, bool sortIntoYear = false)
|
||||
{
|
||||
if (year != default)
|
||||
{
|
||||
upgradeRecords.RemoveAll(x => x.Date.Year != year);
|
||||
}
|
||||
return upgradeRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
if (sortIntoYear)
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
return upgradeRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key.Month,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
|
||||
Year = x.Key.Year,
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
} else
|
||||
{
|
||||
return upgradeRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
}
|
||||
}
|
||||
public IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0)
|
||||
public IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0, bool sortIntoYear = false)
|
||||
{
|
||||
if (year != default)
|
||||
{
|
||||
gasRecords.RemoveAll(x => x.Date.Year != year);
|
||||
}
|
||||
return gasRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
if (sortIntoYear)
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
return gasRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key.Month,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
|
||||
Year = x.Key.Year,
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
} else
|
||||
{
|
||||
return gasRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
}
|
||||
}
|
||||
public IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0)
|
||||
public IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0, bool sortIntoYear = false)
|
||||
{
|
||||
if (year != default)
|
||||
{
|
||||
taxRecords.RemoveAll(x => x.Date.Year != year);
|
||||
}
|
||||
return taxRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
if (sortIntoYear)
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
return taxRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key.Month,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
|
||||
Year = x.Key.Year,
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
} else
|
||||
{
|
||||
return taxRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||
{
|
||||
MonthId = x.Key,
|
||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||
Cost = x.Sum(y => y.Cost)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using CarCareTracker.Models;
|
||||
using CsvHelper;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
{
|
||||
@@ -9,15 +12,18 @@ namespace CarCareTracker.Helper
|
||||
/// </summary>
|
||||
public static class StaticHelper
|
||||
{
|
||||
public static string VersionNumber = "1.3.9";
|
||||
public static string DbName = "data/cartracker.db";
|
||||
public static string UserConfigPath = "config/userConfig.json";
|
||||
public static string GenericErrorMessage = "An error occurred, please try again later";
|
||||
public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
|
||||
public static string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
|
||||
public static string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json";
|
||||
public static string TranslationPath = "https://hargata.github.io/lubelog_translations";
|
||||
public static string TranslationDirectoryPath = $"{TranslationPath}/directory.json";
|
||||
public const string VersionNumber = "1.4.4";
|
||||
public const string DbName = "data/cartracker.db";
|
||||
public const string UserConfigPath = "data/config/userConfig.json";
|
||||
public const string LegacyUserConfigPath = "config/userConfig.json";
|
||||
public const string AdditionalWidgetsPath = "data/widgets.html";
|
||||
public const string GenericErrorMessage = "An error occurred, please try again later";
|
||||
public const string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
|
||||
public const string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
|
||||
public const string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json";
|
||||
public const string TranslationPath = "https://hargata.github.io/lubelog_translations";
|
||||
public const string TranslationDirectoryPath = $"{TranslationPath}/directory.json";
|
||||
public const string ReportNote = "Report generated by LubeLogger, a Free and Open Source Vehicle Maintenance Tracker - LubeLogger.com";
|
||||
public static string GetTitleCaseReminderUrgency(ReminderUrgency input)
|
||||
{
|
||||
switch (input)
|
||||
@@ -32,6 +38,66 @@ namespace CarCareTracker.Helper
|
||||
return input.ToString();
|
||||
}
|
||||
}
|
||||
public static string GetTitleCaseReminderUrgency(string input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case "NotUrgent":
|
||||
return "Not Urgent";
|
||||
case "VeryUrgent":
|
||||
return "Very Urgent";
|
||||
case "PastDue":
|
||||
return "Past Due";
|
||||
default:
|
||||
return input;
|
||||
}
|
||||
}
|
||||
public static string GetReminderUrgencyColor(ReminderUrgency input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case ReminderUrgency.NotUrgent:
|
||||
return "text-bg-success";
|
||||
case ReminderUrgency.VeryUrgent:
|
||||
return "text-bg-danger";
|
||||
case ReminderUrgency.PastDue:
|
||||
return "text-bg-secondary";
|
||||
default:
|
||||
return "text-bg-warning";
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetPlanRecordColor(PlanPriority input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case PlanPriority.Critical:
|
||||
return "text-bg-danger";
|
||||
case PlanPriority.Normal:
|
||||
return "text-bg-primary";
|
||||
case PlanPriority.Low:
|
||||
return "text-bg-info";
|
||||
default:
|
||||
return "text-bg-primary";
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetPlanRecordProgress(PlanProgress input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case PlanProgress.Backlog:
|
||||
return "Planned";
|
||||
case PlanProgress.InProgress:
|
||||
return "Doing";
|
||||
case PlanProgress.Testing:
|
||||
return "Testing";
|
||||
case PlanProgress.Done:
|
||||
return "Done";
|
||||
default:
|
||||
return input.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static string TruncateStrings(string input, int maxLength = 25)
|
||||
{
|
||||
@@ -177,7 +243,8 @@ namespace CarCareTracker.Helper
|
||||
|
||||
public static List<ExtraField> AddExtraFields(List<ExtraField> recordExtraFields, List<ExtraField> templateExtraFields)
|
||||
{
|
||||
if (!templateExtraFields.Any()) {
|
||||
if (!templateExtraFields.Any())
|
||||
{
|
||||
return new List<ExtraField>();
|
||||
}
|
||||
if (!recordExtraFields.Any())
|
||||
@@ -198,7 +265,7 @@ namespace CarCareTracker.Helper
|
||||
extraField.IsRequired = templateExtraFields.Where(x => x.Name == extraField.Name).First().IsRequired;
|
||||
}
|
||||
//append extra fields
|
||||
foreach(ExtraField extraField in templateExtraFields)
|
||||
foreach (ExtraField extraField in templateExtraFields)
|
||||
{
|
||||
if (!recordFieldNames.Contains(extraField.Name))
|
||||
{
|
||||
@@ -246,7 +313,8 @@ namespace CarCareTracker.Helper
|
||||
if (mailConfig != null && !string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||
{
|
||||
Console.WriteLine($"SMTP Configured for {mailConfig.EmailServer}");
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("SMTP Not Configured");
|
||||
}
|
||||
@@ -254,23 +322,113 @@ namespace CarCareTracker.Helper
|
||||
Console.WriteLine($"Message Of The Day: {motd}");
|
||||
if (string.IsNullOrWhiteSpace(CultureInfo.CurrentCulture.Name))
|
||||
{
|
||||
Console.WriteLine("No Locale or Culture Configured for LubeLogger, Check Environment Variables");
|
||||
Console.WriteLine("WARNING: No Locale or Culture Configured for LubeLogger, Check Environment Variables");
|
||||
}
|
||||
//Create folders if they don't exist.
|
||||
if (!Directory.Exists("data"))
|
||||
{
|
||||
Directory.CreateDirectory("data");
|
||||
Console.WriteLine("Created data directory");
|
||||
}
|
||||
if (!Directory.Exists("data/images"))
|
||||
{
|
||||
Console.WriteLine("Created images directory");
|
||||
Directory.CreateDirectory("data/images");
|
||||
}
|
||||
if (!Directory.Exists("data/documents"))
|
||||
{
|
||||
Directory.CreateDirectory("data/documents");
|
||||
Console.WriteLine("Created documents directory");
|
||||
}
|
||||
if (!Directory.Exists("data/translations"))
|
||||
{
|
||||
Directory.CreateDirectory("data/translations");
|
||||
Console.WriteLine("Created translations directory");
|
||||
}
|
||||
if (!Directory.Exists("data/temp"))
|
||||
{
|
||||
Directory.CreateDirectory("data/temp");
|
||||
Console.WriteLine("Created translations directory");
|
||||
}
|
||||
if (!Directory.Exists("data/config"))
|
||||
{
|
||||
Directory.CreateDirectory("data/config");
|
||||
Console.WriteLine("Created config directory");
|
||||
}
|
||||
}
|
||||
public static async void NotifyAsync(string webhookURL, int vehicleId, string username, string action)
|
||||
public static void CheckMigration(string webRootPath, string webContentPath)
|
||||
{
|
||||
//check if current working directory differs from content root.
|
||||
if (Directory.GetCurrentDirectory() != webContentPath)
|
||||
{
|
||||
Console.WriteLine("WARNING: The Working Directory differs from the Web Content Path");
|
||||
Console.WriteLine($"Working Directory: {Directory.GetCurrentDirectory()}");
|
||||
Console.WriteLine($"Web Content Path: {webContentPath}");
|
||||
}
|
||||
//migrates all user-uploaded files from webroot to new data folder
|
||||
//images
|
||||
var imagePath = Path.Combine(webRootPath, "images");
|
||||
var docsPath = Path.Combine(webRootPath, "documents");
|
||||
var translationPath = Path.Combine(webRootPath, "translations");
|
||||
var tempPath = Path.Combine(webRootPath, "temp");
|
||||
if (File.Exists(LegacyUserConfigPath))
|
||||
{
|
||||
File.Move(LegacyUserConfigPath, UserConfigPath, true);
|
||||
}
|
||||
if (Directory.Exists(imagePath))
|
||||
{
|
||||
foreach (string fileToMove in Directory.GetFiles(imagePath))
|
||||
{
|
||||
var newFilePath = $"data/images/{Path.GetFileName(fileToMove)}";
|
||||
File.Move(fileToMove, newFilePath, true);
|
||||
Console.WriteLine($"Migrated Image: {Path.GetFileName(fileToMove)}");
|
||||
}
|
||||
}
|
||||
if (Directory.Exists(docsPath))
|
||||
{
|
||||
foreach (string fileToMove in Directory.GetFiles(docsPath))
|
||||
{
|
||||
var newFilePath = $"data/documents/{Path.GetFileName(fileToMove)}";
|
||||
File.Move(fileToMove, newFilePath, true);
|
||||
Console.WriteLine($"Migrated Document: {Path.GetFileName(fileToMove)}");
|
||||
}
|
||||
}
|
||||
if (Directory.Exists(translationPath))
|
||||
{
|
||||
foreach (string fileToMove in Directory.GetFiles(translationPath))
|
||||
{
|
||||
var newFilePath = $"data/translations/{Path.GetFileName(fileToMove)}";
|
||||
File.Move(fileToMove, newFilePath, true);
|
||||
Console.WriteLine($"Migrated Translation: {Path.GetFileName(fileToMove)}");
|
||||
}
|
||||
}
|
||||
if (Directory.Exists(tempPath))
|
||||
{
|
||||
foreach (string fileToMove in Directory.GetFiles(tempPath))
|
||||
{
|
||||
var newFilePath = $"data/temp/{Path.GetFileName(fileToMove)}";
|
||||
File.Move(fileToMove, newFilePath, true);
|
||||
Console.WriteLine($"Migrated Temp File: {Path.GetFileName(fileToMove)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
public static async void NotifyAsync(string webhookURL, WebHookPayload webHookPayload)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(webhookURL))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var httpClient = new HttpClient();
|
||||
var httpParams = new Dictionary<string, string>
|
||||
{
|
||||
{ "vehicleId", vehicleId.ToString() },
|
||||
{ "username", username },
|
||||
{ "action", action },
|
||||
};
|
||||
httpClient.PostAsJsonAsync(webhookURL, httpParams);
|
||||
if (webhookURL.StartsWith("discord://"))
|
||||
{
|
||||
webhookURL = webhookURL.Replace("discord://", "https://"); //cleanurl
|
||||
//format to discord
|
||||
httpClient.PostAsJsonAsync(webhookURL, DiscordWebHook.FromWebHookPayload(webHookPayload));
|
||||
}
|
||||
else
|
||||
{
|
||||
httpClient.PostAsJsonAsync(webhookURL, webHookPayload);
|
||||
}
|
||||
}
|
||||
public static string GetImportModeIcon(ImportMode importMode)
|
||||
{
|
||||
@@ -305,12 +463,14 @@ namespace CarCareTracker.Helper
|
||||
if (vehicle.VehicleIdentifier == "LicensePlate")
|
||||
{
|
||||
return vehicle.LicensePlate;
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vehicle.ExtraFields.Any(x=>x.Name == vehicle.VehicleIdentifier))
|
||||
if (vehicle.ExtraFields.Any(x => x.Name == vehicle.VehicleIdentifier))
|
||||
{
|
||||
return vehicle.ExtraFields?.FirstOrDefault(x=>x.Name == vehicle.VehicleIdentifier)?.Value;
|
||||
} else
|
||||
return vehicle.ExtraFields?.FirstOrDefault(x => x.Name == vehicle.VehicleIdentifier)?.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "N/A";
|
||||
}
|
||||
@@ -337,10 +497,11 @@ namespace CarCareTracker.Helper
|
||||
//Translations
|
||||
public static string GetTranslationDownloadPath(string continent, string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(continent) || string.IsNullOrWhiteSpace(name)){
|
||||
if (string.IsNullOrWhiteSpace(continent) || string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (continent)
|
||||
{
|
||||
@@ -359,8 +520,9 @@ namespace CarCareTracker.Helper
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return string.Empty;
|
||||
} else
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
string cleanedName = name.Contains("_") ? name.Replace("_", "-") : name;
|
||||
@@ -373,7 +535,8 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
return displayName;
|
||||
}
|
||||
} catch (Exception ex)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
@@ -539,6 +702,35 @@ namespace CarCareTracker.Helper
|
||||
_csv.NextRecord();
|
||||
}
|
||||
}
|
||||
public static string HideZeroCost(string input, bool hideZero, string decorations = "")
|
||||
{
|
||||
if (input == 0M.ToString("C2") && hideZero)
|
||||
{
|
||||
return "---";
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(decorations) ? input : $"{input}{decorations}";
|
||||
}
|
||||
}
|
||||
public static string HideZeroCost(decimal input, bool hideZero, string decorations = "")
|
||||
{
|
||||
if (input == default && hideZero)
|
||||
{
|
||||
return "---";
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(decorations) ? input.ToString("C2") : $"{input.ToString("C2")}{decorations}";
|
||||
}
|
||||
}
|
||||
public static JsonSerializerOptions GetInvariantOption()
|
||||
{
|
||||
var serializerOption = new JsonSerializerOptions();
|
||||
serializerOption.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||
serializerOption.Converters.Add(new InvariantConverter());
|
||||
return serializerOption;
|
||||
}
|
||||
public static void WriteGasRecordExportModel(CsvWriter _csv, IEnumerable<GasRecordExportModel> genericRecords)
|
||||
{
|
||||
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
|
||||
@@ -576,5 +768,52 @@ namespace CarCareTracker.Helper
|
||||
_csv.NextRecord();
|
||||
}
|
||||
}
|
||||
public static byte[] RemindersToCalendar(List<ReminderRecordViewModel> reminders)
|
||||
{
|
||||
//converts reminders to iCal file
|
||||
StringBuilder sb = new StringBuilder();
|
||||
//start the calendar item
|
||||
sb.AppendLine("BEGIN:VCALENDAR");
|
||||
sb.AppendLine("VERSION:2.0");
|
||||
sb.AppendLine("PRODID:lubelogger.com");
|
||||
sb.AppendLine("CALSCALE:GREGORIAN");
|
||||
sb.AppendLine("METHOD:PUBLISH");
|
||||
|
||||
//create events.
|
||||
foreach(ReminderRecordViewModel reminder in reminders)
|
||||
{
|
||||
var dtStart = reminder.Date.Date.ToString("yyyyMMddTHHmm00");
|
||||
var dtEnd = reminder.Date.Date.AddDays(1).AddMilliseconds(-1).ToString("yyyyMMddTHHmm00");
|
||||
var calendarUID = new Guid(MD5.HashData(Encoding.UTF8.GetBytes($"{dtStart}_{reminder.Description}")));
|
||||
sb.AppendLine("BEGIN:VEVENT");
|
||||
sb.AppendLine("DTSTAMP:" + DateTime.Now.ToString("yyyyMMddTHHmm00"));
|
||||
sb.AppendLine("UID:" + calendarUID);
|
||||
sb.AppendLine("DTSTART:" + dtStart);
|
||||
sb.AppendLine("DTEND:" + dtEnd);
|
||||
sb.AppendLine($"SUMMARY:{reminder.Description}");
|
||||
sb.AppendLine($"DESCRIPTION:{reminder.Description}");
|
||||
switch (reminder.Urgency)
|
||||
{
|
||||
case ReminderUrgency.NotUrgent:
|
||||
sb.AppendLine("PRIORITY:3");
|
||||
break;
|
||||
case ReminderUrgency.Urgent:
|
||||
sb.AppendLine("PRIORITY:2");
|
||||
break;
|
||||
case ReminderUrgency.VeryUrgent:
|
||||
sb.AppendLine("PRIORITY:1");
|
||||
break;
|
||||
case ReminderUrgency.PastDue:
|
||||
sb.AppendLine("PRIORITY:1");
|
||||
break;
|
||||
}
|
||||
sb.AppendLine("END:VEVENT");
|
||||
}
|
||||
|
||||
//end calendar item
|
||||
sb.AppendLine("END:VCALENDAR");
|
||||
string calendarContent = sb.ToString();
|
||||
return Encoding.UTF8.GetBytes(calendarContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,10 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
foreach(var translation in translationDictionary)
|
||||
{
|
||||
defaultTranslation[translation.Key] = translation.Value;
|
||||
if (defaultTranslation.ContainsKey(translation.Key))
|
||||
{
|
||||
defaultTranslation[translation.Key] = translation.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultTranslation ?? new Dictionary<string, string>();
|
||||
@@ -123,19 +126,21 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
public OperationResponse SaveTranslation(string userLanguage, Dictionary<string, string> translations)
|
||||
{
|
||||
if (userLanguage == "en_US")
|
||||
bool create = bool.Parse(_config["LUBELOGGER_TRANSLATOR"] ?? "false");
|
||||
bool isDefaultLanguage = userLanguage == "en_US";
|
||||
if (isDefaultLanguage && !create)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "The translation file name en_US is reserved." };
|
||||
return OperationResponse.Failed("The translation file name en_US is reserved.");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(userLanguage))
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "File name is not provided." };
|
||||
return OperationResponse.Failed("File name is not provided.");
|
||||
}
|
||||
if (!translations.Any())
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Translation has no data." };
|
||||
return OperationResponse.Failed("Translation has no data.");
|
||||
}
|
||||
var translationFilePath = _fileHelper.GetFullFilePath($"/translations/{userLanguage}.json", false);
|
||||
var translationFilePath = isDefaultLanguage ? _fileHelper.GetFullFilePath($"/defaults/en_US.json") : _fileHelper.GetFullFilePath($"/translations/{userLanguage}.json", false);
|
||||
try
|
||||
{
|
||||
if (File.Exists(translationFilePath))
|
||||
@@ -145,15 +150,21 @@ namespace CarCareTracker.Helper
|
||||
_cache.Remove($"lang_{userLanguage}"); //clear out cache, force a reload from file.
|
||||
} else
|
||||
{
|
||||
//check if directory exists first.
|
||||
var translationDirectory = _fileHelper.GetFullFilePath("translations/", false);
|
||||
if (!Directory.Exists(translationDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(translationDirectory);
|
||||
}
|
||||
//write to file
|
||||
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(translations));
|
||||
}
|
||||
return new OperationResponse { Success = true, Message = "Translation Updated" };
|
||||
return OperationResponse.Succeed("Translation Updated");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||
return OperationResponse.Failed();
|
||||
}
|
||||
}
|
||||
public string ExportTranslation(Dictionary<string, string> translations)
|
||||
@@ -167,7 +178,13 @@ namespace CarCareTracker.Helper
|
||||
Directory.CreateDirectory(uploadDirectory);
|
||||
}
|
||||
var saveFilePath = _fileHelper.GetFullFilePath(tempFileName, false);
|
||||
File.WriteAllText(saveFilePath, JsonSerializer.Serialize(translations));
|
||||
//standardize translation format for export only.
|
||||
Dictionary<string, string> sortedTranslations = new Dictionary<string, string>();
|
||||
foreach (var translation in translations.OrderBy(x => x.Key))
|
||||
{
|
||||
sortedTranslations.Add(translation.Key, translation.Value);
|
||||
};
|
||||
File.WriteAllText(saveFilePath, JsonSerializer.Serialize(sortedTranslations, new JsonSerializerOptions { WriteIndented = true }));
|
||||
return tempFileName;
|
||||
}
|
||||
catch(Exception ex)
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace CarCareTracker.Logic
|
||||
OperationResponse RequestResetPassword(LoginModel credentials);
|
||||
OperationResponse ResetPasswordByUser(LoginModel credentials);
|
||||
OperationResponse ResetUserPassword(LoginModel credentials);
|
||||
OperationResponse SendRegistrationToken(LoginModel credentials);
|
||||
UserData ValidateUserCredentials(LoginModel credentials);
|
||||
UserData ValidateOpenIDUser(LoginModel credentials);
|
||||
bool CheckIfUserIsValid(int userId);
|
||||
@@ -71,13 +72,13 @@ namespace CarCareTracker.Logic
|
||||
var existingUser = _userData.GetUserRecordById(userId);
|
||||
if (existingUser.Id == default)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Invalid user" };
|
||||
return OperationResponse.Failed("Invalid user");
|
||||
}
|
||||
//validate user token
|
||||
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
|
||||
if (existingToken.Id == default || existingToken.EmailAddress != existingUser.EmailAddress)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Invalid Token" };
|
||||
return OperationResponse.Failed("Invalid Token");
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(credentials.UserName) && existingUser.UserName != credentials.UserName)
|
||||
{
|
||||
@@ -85,7 +86,7 @@ namespace CarCareTracker.Logic
|
||||
var existingUserWithUserName = _userData.GetUserRecordByUserName(credentials.UserName);
|
||||
if (existingUserWithUserName.Id != default)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Username already taken" };
|
||||
return OperationResponse.Failed("Username already taken");
|
||||
}
|
||||
existingUser.UserName = credentials.UserName;
|
||||
}
|
||||
@@ -95,7 +96,7 @@ namespace CarCareTracker.Logic
|
||||
var existingUserWithEmailAddress = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
|
||||
if (existingUserWithEmailAddress.Id != default)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "A user with that email already exists" };
|
||||
return OperationResponse.Failed("A user with that email already exists");
|
||||
}
|
||||
existingUser.EmailAddress = credentials.EmailAddress;
|
||||
}
|
||||
@@ -107,7 +108,7 @@ namespace CarCareTracker.Logic
|
||||
//delete token
|
||||
_tokenData.DeleteToken(existingToken.Id);
|
||||
var result = _userData.SaveUserRecord(existingUser);
|
||||
return new OperationResponse { Success = result, Message = result ? "User Updated" : StaticHelper.GenericErrorMessage };
|
||||
return OperationResponse.Conditional(result, "User Updated", string.Empty);
|
||||
}
|
||||
public OperationResponse RegisterOpenIdUser(LoginModel credentials)
|
||||
{
|
||||
@@ -115,21 +116,21 @@ namespace CarCareTracker.Logic
|
||||
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
|
||||
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Invalid Token" };
|
||||
return OperationResponse.Failed("Invalid Token");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(credentials.EmailAddress) || string.IsNullOrWhiteSpace(credentials.UserName))
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Username cannot be blank" };
|
||||
return OperationResponse.Failed("Username cannot be blank");
|
||||
}
|
||||
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
|
||||
if (existingUser.Id != default)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Username already taken" };
|
||||
return OperationResponse.Failed("Username already taken");
|
||||
}
|
||||
var existingUserWithEmail = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
|
||||
if (existingUserWithEmail.Id != default)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "A user with that email already exists" };
|
||||
return OperationResponse.Failed("A user with that email already exists");
|
||||
}
|
||||
_tokenData.DeleteToken(existingToken.Id);
|
||||
var newUser = new UserData()
|
||||
@@ -141,11 +142,11 @@ namespace CarCareTracker.Logic
|
||||
var result = _userData.SaveUserRecord(newUser);
|
||||
if (result)
|
||||
{
|
||||
return new OperationResponse { Success = true, Message = "You will be logged in briefly." };
|
||||
return OperationResponse.Succeed("You will be logged in briefly.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
|
||||
return OperationResponse.Failed("Something went wrong, please try again later.");
|
||||
}
|
||||
}
|
||||
//handles user registration
|
||||
@@ -155,22 +156,22 @@ namespace CarCareTracker.Logic
|
||||
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
|
||||
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Invalid Token" };
|
||||
return OperationResponse.Failed("Invalid Token");
|
||||
}
|
||||
//token is valid, check if username and password is acceptable and that username is unique.
|
||||
if (string.IsNullOrWhiteSpace(credentials.EmailAddress) || string.IsNullOrWhiteSpace(credentials.UserName) || string.IsNullOrWhiteSpace(credentials.Password))
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Neither username nor password can be blank" };
|
||||
return OperationResponse.Failed("Neither username nor password can be blank");
|
||||
}
|
||||
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
|
||||
if (existingUser.Id != default)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Username already taken" };
|
||||
return OperationResponse.Failed("Username already taken");
|
||||
}
|
||||
var existingUserWithEmail = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
|
||||
if (existingUserWithEmail.Id != default)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "A user with that email already exists" };
|
||||
return OperationResponse.Failed("A user with that email already exists");
|
||||
}
|
||||
//username is unique then we delete the token and create the user.
|
||||
_tokenData.DeleteToken(existingToken.Id);
|
||||
@@ -183,11 +184,21 @@ namespace CarCareTracker.Logic
|
||||
var result = _userData.SaveUserRecord(newUser);
|
||||
if (result)
|
||||
{
|
||||
return new OperationResponse { Success = true, Message = "You will be redirected to the login page briefly." };
|
||||
return OperationResponse.Succeed("You will be redirected to the login page briefly.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
|
||||
return OperationResponse.Failed();
|
||||
}
|
||||
}
|
||||
public OperationResponse SendRegistrationToken(LoginModel credentials)
|
||||
{
|
||||
if (_configHelper.GetServerOpenRegistration())
|
||||
{
|
||||
return GenerateUserToken(credentials.EmailAddress, true);
|
||||
} else
|
||||
{
|
||||
return OperationResponse.Failed("Open Registration Disabled");
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
@@ -205,24 +216,24 @@ namespace CarCareTracker.Logic
|
||||
}
|
||||
//for security purposes we want to always return true for this method.
|
||||
//otherwise someone can spam the reset password method to sniff out users.
|
||||
return new OperationResponse { Success = true, Message = "If your user exists in the system you should receive an email shortly with instructions on how to proceed." };
|
||||
return OperationResponse.Succeed("If your user exists in the system you should receive an email shortly with instructions on how to proceed.");
|
||||
}
|
||||
public OperationResponse ResetPasswordByUser(LoginModel credentials)
|
||||
{
|
||||
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
|
||||
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Invalid Token" };
|
||||
return OperationResponse.Failed("Invalid Token");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(credentials.Password))
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "New Password cannot be blank" };
|
||||
return OperationResponse.Failed("New Password cannot be blank");
|
||||
}
|
||||
//if token is valid.
|
||||
var existingUser = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
|
||||
if (existingUser.Id == default)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Unable to locate user" };
|
||||
return OperationResponse.Failed("Unable to locate user");
|
||||
}
|
||||
existingUser.Password = GetHash(credentials.Password);
|
||||
var result = _userData.SaveUserRecord(existingUser);
|
||||
@@ -230,10 +241,10 @@ namespace CarCareTracker.Logic
|
||||
_tokenData.DeleteToken(existingToken.Id);
|
||||
if (result)
|
||||
{
|
||||
return new OperationResponse { Success = true, Message = "Password resetted, you will be redirected to login page shortly." };
|
||||
return OperationResponse.Succeed("Password resetted, you will be redirected to login page shortly.");
|
||||
} else
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||
return OperationResponse.Failed();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
@@ -310,7 +321,18 @@ namespace CarCareTracker.Logic
|
||||
var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress);
|
||||
if (existingToken.Id != default)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "There is an existing token tied to this email address" };
|
||||
if (autoNotify) //re-send email
|
||||
{
|
||||
var notificationResult = _mailHelper.NotifyUserForRegistration(emailAddress, existingToken.Body);
|
||||
if (notificationResult.Success)
|
||||
{
|
||||
return OperationResponse.Failed($"There is an existing token tied to {emailAddress}, a new email has been sent out");
|
||||
} else
|
||||
{
|
||||
return notificationResult;
|
||||
}
|
||||
}
|
||||
return OperationResponse.Failed($"There is an existing token tied to {emailAddress}");
|
||||
}
|
||||
var token = new Token()
|
||||
{
|
||||
@@ -323,16 +345,16 @@ namespace CarCareTracker.Logic
|
||||
result = _mailHelper.NotifyUserForRegistration(emailAddress, token.Body).Success;
|
||||
if (!result)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Token Generated, but Email failed to send, please check your SMTP settings." };
|
||||
return OperationResponse.Failed("Token Generated, but Email failed to send, please check your SMTP settings.");
|
||||
}
|
||||
}
|
||||
if (result)
|
||||
{
|
||||
return new OperationResponse { Success = true, Message = "Token Generated!" };
|
||||
return OperationResponse.Succeed("Token Generated!");
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||
return OperationResponse.Failed();
|
||||
}
|
||||
}
|
||||
public bool DeleteUserToken(int tokenId)
|
||||
@@ -351,18 +373,18 @@ namespace CarCareTracker.Logic
|
||||
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
|
||||
if (existingUser.Id == default)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Unable to find user" };
|
||||
return OperationResponse.Failed("Unable to find user");
|
||||
}
|
||||
var newPassword = Guid.NewGuid().ToString().Substring(0, 8);
|
||||
existingUser.Password = GetHash(newPassword);
|
||||
var result = _userData.SaveUserRecord(existingUser);
|
||||
if (result)
|
||||
{
|
||||
return new OperationResponse { Success = true, Message = newPassword };
|
||||
return OperationResponse.Succeed(newPassword);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
|
||||
return OperationResponse.Failed();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -51,16 +51,16 @@ namespace CarCareTracker.Logic
|
||||
var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(existingUser.Id, vehicleId);
|
||||
if (userAccess != null)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "User is already a collaborator" };
|
||||
return OperationResponse.Failed("User is already a collaborator");
|
||||
}
|
||||
var result = AddUserAccessToVehicle(existingUser.Id, vehicleId);
|
||||
if (result)
|
||||
{
|
||||
return new OperationResponse { Success = true, Message = "Collaborator Added" };
|
||||
return OperationResponse.Succeed("Collaborator Added");
|
||||
}
|
||||
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||
return OperationResponse.Failed();
|
||||
}
|
||||
return new OperationResponse { Success = false, Message = $"Unable to find user {username} in the system" };
|
||||
return OperationResponse.Failed($"Unable to find user {username} in the system");
|
||||
}
|
||||
public bool DeleteCollaboratorFromVehicle(int userId, int vehicleId)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Controllers;
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
|
||||
@@ -12,8 +13,13 @@ namespace CarCareTracker.Logic
|
||||
int GetMaxMileage(VehicleRecords vehicleRecords);
|
||||
int GetMinMileage(int vehicleId);
|
||||
int GetMinMileage(VehicleRecords vehicleRecords);
|
||||
int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords);
|
||||
int GetOwnershipDays(string purchaseDate, string soldDate, int year, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords);
|
||||
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, int currentMileage);
|
||||
List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles);
|
||||
List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar);
|
||||
List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone);
|
||||
bool UpdateRecurringTaxes(int vehicleId);
|
||||
void RestoreSupplyRecordsByUsage(List<SupplyUsageHistory> supplyUsage, string usageDescription);
|
||||
}
|
||||
public class VehicleLogic: IVehicleLogic
|
||||
{
|
||||
@@ -24,7 +30,12 @@ namespace CarCareTracker.Logic
|
||||
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
|
||||
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
|
||||
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||
private readonly IPlanRecordDataAccess _planRecordDataAccess;
|
||||
private readonly IReminderHelper _reminderHelper;
|
||||
private readonly IVehicleDataAccess _dataAccess;
|
||||
private readonly ISupplyRecordDataAccess _supplyRecordDataAccess;
|
||||
private readonly ILogger<VehicleLogic> _logger;
|
||||
|
||||
public VehicleLogic(
|
||||
IServiceRecordDataAccess serviceRecordDataAccess,
|
||||
IGasRecordDataAccess gasRecordDataAccess,
|
||||
@@ -33,7 +44,11 @@ namespace CarCareTracker.Logic
|
||||
ITaxRecordDataAccess taxRecordDataAccess,
|
||||
IOdometerRecordDataAccess odometerRecordDataAccess,
|
||||
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||
IReminderHelper reminderHelper
|
||||
IPlanRecordDataAccess planRecordDataAccess,
|
||||
IReminderHelper reminderHelper,
|
||||
IVehicleDataAccess dataAccess,
|
||||
ISupplyRecordDataAccess supplyRecordDataAccess,
|
||||
ILogger<VehicleLogic> logger
|
||||
) {
|
||||
_serviceRecordDataAccess = serviceRecordDataAccess;
|
||||
_gasRecordDataAccess = gasRecordDataAccess;
|
||||
@@ -41,8 +56,12 @@ namespace CarCareTracker.Logic
|
||||
_upgradeRecordDataAccess = upgradeRecordDataAccess;
|
||||
_taxRecordDataAccess = taxRecordDataAccess;
|
||||
_odometerRecordDataAccess = odometerRecordDataAccess;
|
||||
_planRecordDataAccess = planRecordDataAccess;
|
||||
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||
_reminderHelper = reminderHelper;
|
||||
_dataAccess = dataAccess;
|
||||
_supplyRecordDataAccess = supplyRecordDataAccess;
|
||||
_logger = logger;
|
||||
}
|
||||
public VehicleRecords GetVehicleRecords(int vehicleId)
|
||||
{
|
||||
@@ -180,22 +199,44 @@ namespace CarCareTracker.Logic
|
||||
}
|
||||
return numbersArray.Any() ? numbersArray.Min() : 0;
|
||||
}
|
||||
public int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords)
|
||||
public int GetOwnershipDays(string purchaseDate, string soldDate, int year, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords)
|
||||
{
|
||||
var startDate = DateTime.Now;
|
||||
var endDate = DateTime.Now;
|
||||
if (!string.IsNullOrWhiteSpace(soldDate))
|
||||
bool usePurchaseDate = false;
|
||||
bool useSoldDate = false;
|
||||
if (!string.IsNullOrWhiteSpace(soldDate) && DateTime.TryParse(soldDate, out DateTime vehicleSoldDate))
|
||||
{
|
||||
endDate = DateTime.Parse(soldDate);
|
||||
if (year == default || year >= vehicleSoldDate.Year) //All Time is selected or the selected year is greater or equal to the year the vehicle is sold
|
||||
{
|
||||
endDate = vehicleSoldDate; //cap end date to vehicle sold date.
|
||||
useSoldDate = true;
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(purchaseDate))
|
||||
if (!string.IsNullOrWhiteSpace(purchaseDate) && DateTime.TryParse(purchaseDate, out DateTime vehiclePurchaseDate))
|
||||
{
|
||||
//if purchase date is provided, then we just have to subtract the begin date to end date and return number of months
|
||||
startDate = DateTime.Parse(purchaseDate);
|
||||
if (year == default || year <= vehiclePurchaseDate.Year) //All Time is selected or the selected year is less or equal to the year the vehicle is purchased
|
||||
{
|
||||
startDate = vehiclePurchaseDate; //cap start date to vehicle purchase date
|
||||
usePurchaseDate = true;
|
||||
}
|
||||
}
|
||||
if (year != default)
|
||||
{
|
||||
var calendarYearStart = new DateTime(year, 1, 1);
|
||||
var calendarYearEnd = new DateTime(year + 1, 1, 1);
|
||||
if (!useSoldDate)
|
||||
{
|
||||
endDate = endDate > calendarYearEnd ? calendarYearEnd : endDate;
|
||||
}
|
||||
if (!usePurchaseDate)
|
||||
{
|
||||
startDate = startDate > calendarYearStart ? calendarYearStart : startDate;
|
||||
}
|
||||
var timeElapsed = (int)Math.Floor((endDate - startDate).TotalDays);
|
||||
return timeElapsed;
|
||||
}
|
||||
var dateArray = new List<DateTime>();
|
||||
var dateArray = new List<DateTime>() { startDate };
|
||||
dateArray.AddRange(serviceRecords.Select(x => x.Date));
|
||||
dateArray.AddRange(repairRecords.Select(x => x.Date));
|
||||
dateArray.AddRange(gasRecords.Select(x => x.Date));
|
||||
@@ -218,5 +259,191 @@ namespace CarCareTracker.Logic
|
||||
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
|
||||
return results.Any(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue);
|
||||
}
|
||||
|
||||
public List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles)
|
||||
{
|
||||
List<VehicleInfo> apiResult = new List<VehicleInfo>();
|
||||
|
||||
foreach (Vehicle vehicle in vehicles)
|
||||
{
|
||||
var currentMileage = GetMaxMileage(vehicle.Id);
|
||||
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
|
||||
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
|
||||
|
||||
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicle.Id);
|
||||
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicle.Id);
|
||||
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicle.Id);
|
||||
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicle.Id);
|
||||
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicle.Id);
|
||||
var planRecords = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id);
|
||||
|
||||
var resultToAdd = new VehicleInfo()
|
||||
{
|
||||
VehicleData = vehicle,
|
||||
LastReportedOdometer = currentMileage,
|
||||
ServiceRecordCount = serviceRecords.Count(),
|
||||
ServiceRecordCost = serviceRecords.Sum(x => x.Cost),
|
||||
RepairRecordCount = repairRecords.Count(),
|
||||
RepairRecordCost = repairRecords.Sum(x => x.Cost),
|
||||
UpgradeRecordCount = upgradeRecords.Count(),
|
||||
UpgradeRecordCost = upgradeRecords.Sum(x => x.Cost),
|
||||
GasRecordCount = gasRecords.Count(),
|
||||
GasRecordCost = gasRecords.Sum(x => x.Cost),
|
||||
TaxRecordCount = taxRecords.Count(),
|
||||
TaxRecordCost = taxRecords.Sum(x => x.Cost),
|
||||
VeryUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.VeryUrgent),
|
||||
PastDueReminderCount = results.Count(x => x.Urgency == ReminderUrgency.PastDue),
|
||||
UrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.Urgent),
|
||||
NotUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.NotUrgent),
|
||||
PlanRecordBackLogCount = planRecords.Count(x => x.Progress == PlanProgress.Backlog),
|
||||
PlanRecordInProgressCount = planRecords.Count(x => x.Progress == PlanProgress.InProgress),
|
||||
PlanRecordTestingCount = planRecords.Count(x => x.Progress == PlanProgress.Testing),
|
||||
PlanRecordDoneCount = planRecords.Count(x => x.Progress == PlanProgress.Done)
|
||||
};
|
||||
//set next reminder
|
||||
if (results.Any(x => (x.Metric == ReminderMetric.Date || x.Metric == ReminderMetric.Both) && x.Date >= DateTime.Now.Date))
|
||||
{
|
||||
resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
|
||||
}
|
||||
else if (results.Any(x => (x.Metric == ReminderMetric.Odometer || x.Metric == ReminderMetric.Both) && x.Mileage >= currentMileage))
|
||||
{
|
||||
resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
|
||||
}
|
||||
apiResult.Add(resultToAdd);
|
||||
}
|
||||
return apiResult;
|
||||
}
|
||||
public List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar)
|
||||
{
|
||||
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>();
|
||||
foreach (Vehicle vehicle in vehicles)
|
||||
{
|
||||
var vehicleReminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
|
||||
if (isCalendar)
|
||||
{
|
||||
vehicleReminders.RemoveAll(x => x.Metric == ReminderMetric.Odometer);
|
||||
//we don't care about mileages so we can basically fake the current vehicle mileage.
|
||||
}
|
||||
if (vehicleReminders.Any())
|
||||
{
|
||||
var vehicleMileage = isCalendar ? 0 : GetMaxMileage(vehicle.Id);
|
||||
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(vehicleReminders, vehicleMileage, DateTime.Now);
|
||||
reminderUrgency = reminderUrgency.Select(x => new ReminderRecordViewModel { Id = x.Id, Metric = x.Metric, Date = x.Date, Notes = x.Notes, Mileage = x.Mileage, Urgency = x.Urgency, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" }).ToList();
|
||||
reminders.AddRange(reminderUrgency);
|
||||
}
|
||||
}
|
||||
return reminders.OrderByDescending(x=>x.Urgency).ToList();
|
||||
}
|
||||
public List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone)
|
||||
{
|
||||
List<PlanRecord> plans = new List<PlanRecord>();
|
||||
foreach (Vehicle vehicle in vehicles)
|
||||
{
|
||||
var vehiclePlans = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id);
|
||||
if (excludeDone)
|
||||
{
|
||||
vehiclePlans.RemoveAll(x => x.Progress == PlanProgress.Done);
|
||||
}
|
||||
if (vehiclePlans.Any())
|
||||
{
|
||||
var convertedPlans = vehiclePlans.Select(x => new PlanRecord { ImportMode = x.ImportMode, Priority = x.Priority, Progress = x.Progress, Notes = x.Notes, RequisitionHistory = x.RequisitionHistory, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" });
|
||||
plans.AddRange(convertedPlans);
|
||||
}
|
||||
}
|
||||
return plans.OrderBy(x => x.Priority).ThenBy(x=>x.Progress).ToList();
|
||||
}
|
||||
public bool UpdateRecurringTaxes(int vehicleId)
|
||||
{
|
||||
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||
if (!string.IsNullOrWhiteSpace(vehicleData.SoldDate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool RecurringTaxIsOutdated(TaxRecord taxRecord)
|
||||
{
|
||||
var monthInterval = taxRecord.RecurringInterval != ReminderMonthInterval.Other ? (int)taxRecord.RecurringInterval : taxRecord.CustomMonthInterval;
|
||||
bool addDays = taxRecord.RecurringInterval == ReminderMonthInterval.Other && taxRecord.CustomMonthIntervalUnit == ReminderIntervalUnit.Days;
|
||||
return addDays ? DateTime.Now > taxRecord.Date.AddDays(monthInterval) : DateTime.Now > taxRecord.Date.AddMonths(monthInterval);
|
||||
}
|
||||
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||
var outdatedRecurringFees = result.Where(x => x.IsRecurring && RecurringTaxIsOutdated(x));
|
||||
if (outdatedRecurringFees.Any())
|
||||
{
|
||||
var success = false;
|
||||
foreach (TaxRecord recurringFee in outdatedRecurringFees)
|
||||
{
|
||||
var monthInterval = recurringFee.RecurringInterval != ReminderMonthInterval.Other ? (int)recurringFee.RecurringInterval : recurringFee.CustomMonthInterval;
|
||||
bool isOutdated = true;
|
||||
bool addDays = recurringFee.RecurringInterval == ReminderMonthInterval.Other && recurringFee.CustomMonthIntervalUnit == ReminderIntervalUnit.Days;
|
||||
//update the original outdated tax record
|
||||
recurringFee.IsRecurring = false;
|
||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
|
||||
//month multiplier for severely outdated monthly tax records.
|
||||
int monthMultiplier = 1;
|
||||
var originalDate = recurringFee.Date;
|
||||
while (isOutdated)
|
||||
{
|
||||
try
|
||||
{
|
||||
var nextDate = addDays ? originalDate.AddDays(monthInterval * monthMultiplier) : originalDate.AddMonths(monthInterval * monthMultiplier);
|
||||
monthMultiplier++;
|
||||
var nextnextDate = addDays ? originalDate.AddDays(monthInterval * monthMultiplier) : originalDate.AddMonths(monthInterval * monthMultiplier);
|
||||
recurringFee.Date = nextDate;
|
||||
recurringFee.Id = default; //new record
|
||||
recurringFee.IsRecurring = DateTime.Now <= nextnextDate;
|
||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
|
||||
isOutdated = !recurringFee.IsRecurring;
|
||||
success = true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
isOutdated = false; //break out of loop if something broke.
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
return false; //no outdated recurring tax records.
|
||||
}
|
||||
public void RestoreSupplyRecordsByUsage(List<SupplyUsageHistory> supplyUsage, string usageDescription)
|
||||
{
|
||||
foreach (SupplyUsageHistory supply in supplyUsage)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (supply.Id == default)
|
||||
{
|
||||
continue; //no id, skip current supply.
|
||||
}
|
||||
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.Id);
|
||||
if (result != null && result.Id != default)
|
||||
{
|
||||
//supply exists, re-add the quantity and cost
|
||||
result.Quantity += supply.Quantity;
|
||||
result.Cost += supply.Cost;
|
||||
var requisitionRecord = new SupplyUsageHistory
|
||||
{
|
||||
Id = supply.Id,
|
||||
Date = DateTime.Now.Date,
|
||||
Description = $"Restored from {usageDescription}",
|
||||
Quantity = supply.Quantity,
|
||||
Cost = supply.Cost
|
||||
};
|
||||
result.RequisitionHistory.Add(requisitionRecord);
|
||||
//save
|
||||
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError($"Unable to find supply with id {supply.Id}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error restoring supply with id {supply.Id} : {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,21 @@ namespace CarCareTracker.MapProfile
|
||||
public ImportMapper()
|
||||
{
|
||||
Map(m => m.Date).Name(["date", "fuelup_date"]);
|
||||
Map(m => m.Day).Name(["day"]);
|
||||
Map(m => m.Month).Name(["month"]);
|
||||
Map(m => m.Year).Name(["year"]);
|
||||
Map(m => m.DateCreated).Name(["datecreated"]);
|
||||
Map(m => m.DateModified).Name(["datemodified"]);
|
||||
Map(m => m.InitialOdometer).Name(["initialodometer"]);
|
||||
Map(m => m.Odometer).Name(["odometer"]);
|
||||
Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed"]);
|
||||
Map(m => m.Odometer).Name(["odometer", "odo"]);
|
||||
Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed", "qty"]);
|
||||
Map(m => m.Cost).Name(["cost", "total cost", "totalcost", "total price"]);
|
||||
Map(m => m.Notes).Name("notes", "note");
|
||||
Map(m => m.Price).Name(["price"]);
|
||||
Map(m => m.PartialFuelUp).Name(["partial_fuelup"]);
|
||||
Map(m => m.PartialFuelUp).Name(["partial_fuelup", "partial tank", "partial_fill"]);
|
||||
Map(m => m.IsFillToFull).Name(["isfilltofull", "filled up"]);
|
||||
Map(m => m.Description).Name(["description"]);
|
||||
Map(m => m.MissedFuelUp).Name(["missed_fuelup", "missedfuelup"]);
|
||||
Map(m => m.MissedFuelUp).Name(["missed_fuelup", "missedfuelup", "missed fill up", "missed_fill"]);
|
||||
Map(m => m.PartSupplier).Name(["partsupplier"]);
|
||||
Map(m => m.PartQuantity).Name(["partquantity"]);
|
||||
Map(m => m.PartNumber).Name(["partnumber"]);
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
|
||||
@@ -27,7 +28,7 @@ namespace CarCareTracker.Middleware
|
||||
_httpContext = httpContext;
|
||||
_dataProtector = securityProvider.CreateProtector("login");
|
||||
_loginLogic = loginLogic;
|
||||
enableAuth = bool.Parse(configuration["EnableAuth"]);
|
||||
enableAuth = bool.Parse(configuration["EnableAuth"] ?? "false");
|
||||
}
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
@@ -42,7 +43,7 @@ namespace CarCareTracker.Middleware
|
||||
new(ClaimTypes.Role, nameof(UserData.IsRootUser))
|
||||
};
|
||||
appIdentity.AddClaims(userIdentity);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
else
|
||||
@@ -59,7 +60,7 @@ namespace CarCareTracker.Middleware
|
||||
{
|
||||
var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim();
|
||||
byte[] data = Convert.FromBase64String(cleanedHeader);
|
||||
string decodedString = System.Text.Encoding.UTF8.GetString(data);
|
||||
string decodedString = Encoding.UTF8.GetString(data);
|
||||
var splitString = decodedString.Split(":");
|
||||
if (splitString.Count() != 2)
|
||||
{
|
||||
@@ -85,7 +86,7 @@ namespace CarCareTracker.Middleware
|
||||
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
||||
}
|
||||
appIdentity.AddClaims(userIdentity);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
}
|
||||
@@ -133,7 +134,7 @@ namespace CarCareTracker.Middleware
|
||||
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
||||
}
|
||||
appIdentity.AddClaims(userIdentity);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
}
|
||||
@@ -153,6 +154,7 @@ namespace CarCareTracker.Middleware
|
||||
if (value.ToString().ToLower() == "api")
|
||||
{
|
||||
Response.StatusCode = 401;
|
||||
Response.Headers.Append("WWW-Authenticate", "Basic");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
138
Models/API/TypeConverter.cs
Normal file
138
Models/API/TypeConverter.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class DummyType
|
||||
{
|
||||
|
||||
}
|
||||
class InvariantConverter : JsonConverter<DummyType>
|
||||
{
|
||||
public override void Write(Utf8JsonWriter writer, DummyType value, JsonSerializerOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override DummyType? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
class FromDateOptional: JsonConverter<string>
|
||||
{
|
||||
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var tokenType = reader.TokenType;
|
||||
if (tokenType == JsonTokenType.String)
|
||||
{
|
||||
return reader.GetString();
|
||||
}
|
||||
else if (tokenType == JsonTokenType.Number)
|
||||
{
|
||||
if (reader.TryGetInt64(out long intInput))
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeSeconds(intInput).Date.ToShortDateString();
|
||||
}
|
||||
}
|
||||
return reader.GetString();
|
||||
}
|
||||
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
|
||||
{
|
||||
if (options.Converters.Any(x => x.Type == typeof(DummyType)))
|
||||
{
|
||||
writer.WriteStringValue(DateTime.Parse(value).ToString("yyyy-MM-dd"));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStringValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
class FromDecimalOptional : JsonConverter<string>
|
||||
{
|
||||
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var tokenType = reader.TokenType;
|
||||
if (tokenType == JsonTokenType.String)
|
||||
{
|
||||
return reader.GetString();
|
||||
}
|
||||
else if (tokenType == JsonTokenType.Number) {
|
||||
if (reader.TryGetDecimal(out decimal decimalInput))
|
||||
{
|
||||
return decimalInput.ToString();
|
||||
}
|
||||
}
|
||||
return reader.GetString();
|
||||
}
|
||||
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
|
||||
{
|
||||
if (options.Converters.Any(x=>x.Type == typeof(DummyType)))
|
||||
{
|
||||
writer.WriteNumberValue(decimal.Parse(value));
|
||||
} else
|
||||
{
|
||||
writer.WriteStringValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
class FromIntOptional : JsonConverter<string>
|
||||
{
|
||||
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var tokenType = reader.TokenType;
|
||||
if (tokenType == JsonTokenType.String)
|
||||
{
|
||||
return reader.GetString();
|
||||
}
|
||||
else if (tokenType == JsonTokenType.Number)
|
||||
{
|
||||
if (reader.TryGetInt32(out int intInput))
|
||||
{
|
||||
return intInput.ToString();
|
||||
}
|
||||
}
|
||||
return reader.GetString();
|
||||
}
|
||||
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
|
||||
{
|
||||
if (options.Converters.Any(x => x.Type == typeof(DummyType)))
|
||||
{
|
||||
writer.WriteNumberValue(int.Parse(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStringValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
class FromBoolOptional : JsonConverter<string>
|
||||
{
|
||||
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var tokenType = reader.TokenType;
|
||||
switch (tokenType)
|
||||
{
|
||||
case JsonTokenType.String:
|
||||
return reader.GetString();
|
||||
case JsonTokenType.True:
|
||||
return "True";
|
||||
case JsonTokenType.False:
|
||||
return "False";
|
||||
default:
|
||||
return reader.GetString();
|
||||
}
|
||||
}
|
||||
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
|
||||
{
|
||||
if (options.Converters.Any(x => x.Type == typeof(DummyType)))
|
||||
{
|
||||
writer.WriteBooleanValue(bool.Parse(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStringValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
|
||||
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
|
||||
public bool CopySuppliesAttachment { get; set; } = false;
|
||||
public CollisionRecord ToCollisionRecord() { return new CollisionRecord {
|
||||
Id = Id,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
public string Notes { get; set; }
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public bool IncludeInAverage { get { return MilesPerGallon > 0 || (!IsFillToFull && !MissedFuelUp); } }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public bool IncludeInAverage { get { return MilesPerGallon > 0 || (!IsFillToFull && !MissedFuelUp) || (Mileage == default && !MissedFuelUp); } }
|
||||
}
|
||||
}
|
||||
|
||||
14
Models/Kiosk/KioskViewModel.cs
Normal file
14
Models/Kiosk/KioskViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class KioskViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// List of vehicle ids to exclude from Kiosk Dashboard
|
||||
/// </summary>
|
||||
public List<int> Exclusions { get; set; } = new List<int>();
|
||||
/// <summary>
|
||||
/// Whether to retrieve data for vehicle, plans, or reminder view.
|
||||
/// </summary>
|
||||
public KioskMode KioskMode { get; set; } = KioskMode.Vehicle;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,33 @@
|
||||
namespace CarCareTracker.Models
|
||||
using CarCareTracker.Helper;
|
||||
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class OperationResponse
|
||||
public class OperationResponseBase
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
public class OperationResponse: OperationResponseBase
|
||||
{
|
||||
public static OperationResponse Succeed(string message = "")
|
||||
{
|
||||
return new OperationResponse { Success = true, Message = message };
|
||||
}
|
||||
public static OperationResponse Failed(string message = "")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
message = StaticHelper.GenericErrorMessage;
|
||||
}
|
||||
return new OperationResponse { Success = false, Message = message};
|
||||
}
|
||||
public static OperationResponse Conditional(bool result, string successMessage = "", string errorMessage = "")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(errorMessage))
|
||||
{
|
||||
errorMessage = StaticHelper.GenericErrorMessage;
|
||||
}
|
||||
return new OperationResponse { Success = result, Message = result ? successMessage : errorMessage };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
public decimal Cost { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
|
||||
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
|
||||
public bool CopySuppliesAttachment { get; set; } = false;
|
||||
public PlanRecord ToPlanRecord() { return new PlanRecord {
|
||||
Id = Id,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
|
||||
public int CustomMileageInterval { get; set; } = 0;
|
||||
public int CustomMonthInterval { get; set; } = 0;
|
||||
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
|
||||
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
|
||||
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
|
||||
public int CustomMileageInterval { get; set; } = 0;
|
||||
public int CustomMonthInterval { get; set; } = 0;
|
||||
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
|
||||
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
|
||||
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||
@@ -34,6 +35,7 @@
|
||||
ReminderMonthInterval = ReminderMonthInterval,
|
||||
CustomMileageInterval = CustomMileageInterval,
|
||||
CustomMonthInterval = CustomMonthInterval,
|
||||
CustomMonthIntervalUnit = CustomMonthIntervalUnit,
|
||||
Notes = Notes,
|
||||
Tags = Tags
|
||||
};
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
/// <summary>
|
||||
/// The metric the user selected to calculate the urgency of this reminder.
|
||||
/// </summary>
|
||||
public ReminderMetric UserMetric { get; set; } = ReminderMetric.Date;
|
||||
/// <summary>
|
||||
/// Reason why this reminder is urgent
|
||||
/// </summary>
|
||||
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||
|
||||
8
Models/Report/CostDistanceTableForVehicle.cs
Normal file
8
Models/Report/CostDistanceTableForVehicle.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class CostDistanceTableForVehicle
|
||||
{
|
||||
public string DistanceUnit { get; set; } = "mi.";
|
||||
public List<CostForVehicleByMonth> CostData { get; set; } = new List<CostForVehicleByMonth>();
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
{
|
||||
public class CostForVehicleByMonth
|
||||
{
|
||||
public int Year { get; set; }
|
||||
public int MonthId { get; set; }
|
||||
public string MonthName { get; set; }
|
||||
public decimal Cost { get; set; }
|
||||
public int DistanceTraveled { get; set; }
|
||||
public decimal CostPerDistanceTraveled { get { if (DistanceTraveled > 0) { return Cost / DistanceTraveled; } else { return 0M; } } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,6 @@
|
||||
public string Notes { get; set; }
|
||||
public decimal Cost { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
}
|
||||
}
|
||||
|
||||
13
Models/Report/ReportParameter.cs
Normal file
13
Models/Report/ReportParameter.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class ReportParameter
|
||||
{
|
||||
public List<string> VisibleColumns { get; set; } = new List<string>();
|
||||
public List<string> ExtraFields { get; set; } = new List<string>();
|
||||
public TagFilter TagFilter { get; set; } = TagFilter.Exclude;
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public bool FilterByDateRange { get; set; } = false;
|
||||
public string StartDate { get; set; } = "";
|
||||
public string EndDate { get; set; } = "";
|
||||
}
|
||||
}
|
||||
@@ -8,5 +8,6 @@
|
||||
public ReminderMakeUpForVehicle ReminderMakeUpForVehicle { get; set; } = new ReminderMakeUpForVehicle();
|
||||
public List<int> Years { get; set; } = new List<int>();
|
||||
public List<UserCollaborator> Collaborators { get; set; } = new List<UserCollaborator>();
|
||||
public bool CustomWidgetsConfigured { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{
|
||||
public Vehicle VehicleData { get; set; }
|
||||
public List<GenericReportModel> VehicleHistory { get; set; }
|
||||
public ReportParameter ReportParameters { get; set; }
|
||||
public string Odometer { get; set; }
|
||||
public string MPG { get; set; }
|
||||
public decimal TotalCost { get; set; }
|
||||
@@ -16,5 +17,7 @@
|
||||
public decimal TotalDepreciation { get; set; }
|
||||
public decimal DepreciationPerDay { get; set; }
|
||||
public decimal DepreciationPerMile { get; set; }
|
||||
public string StartDate { get; set; }
|
||||
public string EndDate { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
|
||||
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
|
||||
public bool CopySuppliesAttachment { get; set; } = false;
|
||||
public ServiceRecord ToServiceRecord() { return new ServiceRecord {
|
||||
Id = Id,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace CarCareTracker.Models
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Import model used for importing records via CSV.
|
||||
@@ -6,6 +8,9 @@
|
||||
public class ImportModel
|
||||
{
|
||||
public string Date { get; set; }
|
||||
public string Day { get; set; }
|
||||
public string Month { get; set; }
|
||||
public string Year { get; set; }
|
||||
public string DateCreated { get; set; }
|
||||
public string DateModified { get; set; }
|
||||
public string Type { get; set; }
|
||||
@@ -38,48 +43,75 @@
|
||||
public string Cost { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
}
|
||||
public class GenericRecordExportModel
|
||||
{
|
||||
[JsonConverter(typeof(FromIntOptional))]
|
||||
public string Id { get; set; }
|
||||
[JsonConverter(typeof(FromDateOptional))]
|
||||
public string Date { get; set; }
|
||||
[JsonConverter(typeof(FromIntOptional))]
|
||||
public string Odometer { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
[JsonConverter(typeof(FromDecimalOptional))]
|
||||
public string Cost { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
}
|
||||
public class OdometerRecordExportModel
|
||||
{
|
||||
[JsonConverter(typeof(FromIntOptional))]
|
||||
public string Id { get; set; }
|
||||
[JsonConverter(typeof(FromDateOptional))]
|
||||
public string Date { get; set; }
|
||||
[JsonConverter(typeof(FromIntOptional))]
|
||||
public string InitialOdometer { get; set; }
|
||||
[JsonConverter(typeof(FromIntOptional))]
|
||||
public string Odometer { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
}
|
||||
public class TaxRecordExportModel
|
||||
{
|
||||
[JsonConverter(typeof(FromIntOptional))]
|
||||
public string Id { get; set; }
|
||||
[JsonConverter(typeof(FromDateOptional))]
|
||||
public string Date { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
[JsonConverter(typeof(FromDecimalOptional))]
|
||||
public string Cost { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
}
|
||||
public class GasRecordExportModel
|
||||
{
|
||||
[JsonConverter(typeof(FromIntOptional))]
|
||||
public string Id { get; set; }
|
||||
[JsonConverter(typeof(FromDateOptional))]
|
||||
public string Date { get; set; }
|
||||
[JsonConverter(typeof(FromIntOptional))]
|
||||
public string Odometer { get; set; }
|
||||
[JsonConverter(typeof(FromDecimalOptional))]
|
||||
public string FuelConsumed { get; set; }
|
||||
[JsonConverter(typeof(FromDecimalOptional))]
|
||||
public string Cost { get; set; }
|
||||
[JsonConverter(typeof(FromDecimalOptional))]
|
||||
public string FuelEconomy { get; set; }
|
||||
[JsonConverter(typeof(FromBoolOptional))]
|
||||
public string IsFillToFull { get; set; }
|
||||
[JsonConverter(typeof(FromBoolOptional))]
|
||||
public string MissedFuelUp { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
}
|
||||
public class ReminderExportModel
|
||||
{
|
||||
@@ -87,7 +119,9 @@
|
||||
public string Urgency { get; set; }
|
||||
public string Metric { get; set; }
|
||||
public string Notes { get; set; }
|
||||
[JsonConverter(typeof(FromDateOptional))]
|
||||
public string DueDate { get; set; }
|
||||
[JsonConverter(typeof(FromIntOptional))]
|
||||
public string DueOdometer { get; set; }
|
||||
}
|
||||
public class PlanRecordExportModel
|
||||
@@ -100,6 +134,6 @@
|
||||
public string Priority { get; set; }
|
||||
public string Progress { get; set; }
|
||||
public string Cost { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
}
|
||||
}
|
||||
|
||||
11
Models/Shared/StickerViewModel.cs
Normal file
11
Models/Shared/StickerViewModel.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class StickerViewModel
|
||||
{
|
||||
public ImportMode RecordType { get; set; }
|
||||
public Vehicle VehicleData { get; set; } = new Vehicle();
|
||||
public List<ReminderRecord> ReminderRecords { get; set; } = new List<ReminderRecord>();
|
||||
public List<GenericRecord> GenericRecords { get; set; } = new List<GenericRecord>();
|
||||
public List<SupplyRecord> SupplyRecords { get; set; } = new List<SupplyRecord>();
|
||||
}
|
||||
}
|
||||
253
Models/Shared/WebHookPayload.cs
Normal file
253
Models/Shared/WebHookPayload.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// WebHookPayload Object
|
||||
/// </summary>
|
||||
public class WebHookPayloadBase
|
||||
{
|
||||
public string Type { get; set; } = "";
|
||||
public string Timestamp
|
||||
{
|
||||
get { return DateTime.UtcNow.ToString("O"); }
|
||||
}
|
||||
public Dictionary<string, string> Data { get; set; } = new Dictionary<string, string>();
|
||||
/// <summary>
|
||||
/// Legacy attributes below
|
||||
/// </summary>
|
||||
public string VehicleId { get; set; } = "";
|
||||
public string Username { get; set; } = "";
|
||||
public string Action { get; set; } = "";
|
||||
}
|
||||
public class DiscordWebHook
|
||||
{
|
||||
public string Username { get { return "LubeLogger"; } }
|
||||
[JsonPropertyName("avatar_url")]
|
||||
public string AvatarUrl { get { return "https://hargata.github.io/hargata/lubelogger_logo_small.png"; } }
|
||||
public string Content { get; set; } = "";
|
||||
public static DiscordWebHook FromWebHookPayload(WebHookPayload webHookPayload)
|
||||
{
|
||||
return new DiscordWebHook
|
||||
{
|
||||
Content = webHookPayload.Action,
|
||||
};
|
||||
}
|
||||
}
|
||||
public class WebHookPayload: WebHookPayloadBase
|
||||
{
|
||||
private static string GetFriendlyActionType(string actionType)
|
||||
{
|
||||
var actionTypeParts = actionType.Split('.');
|
||||
if (actionTypeParts.Length == 2)
|
||||
{
|
||||
var recordType = actionTypeParts[0];
|
||||
var recordAction = actionTypeParts[1];
|
||||
switch (recordAction)
|
||||
{
|
||||
case "add":
|
||||
recordAction = "Added";
|
||||
break;
|
||||
case "update":
|
||||
recordAction = "Updated";
|
||||
break;
|
||||
case "delete":
|
||||
recordAction = "Deleted";
|
||||
break;
|
||||
}
|
||||
if (recordType.ToLower().Contains("record"))
|
||||
{
|
||||
var cleanedRecordType = recordType.ToLower().Replace("record", "");
|
||||
cleanedRecordType = $"{char.ToUpper(cleanedRecordType[0])}{cleanedRecordType.Substring(1)} Record";
|
||||
recordType = cleanedRecordType;
|
||||
} else
|
||||
{
|
||||
recordType = $"{char.ToUpper(recordType[0])}{recordType.Substring(1)}";
|
||||
}
|
||||
return $"{recordAction} {recordType}";
|
||||
} else if (actionTypeParts.Length == 3)
|
||||
{
|
||||
var recordType = actionTypeParts[0];
|
||||
var recordAction = actionTypeParts[1];
|
||||
var thirdPart = actionTypeParts[2];
|
||||
switch (recordAction)
|
||||
{
|
||||
case "add":
|
||||
recordAction = "Added";
|
||||
break;
|
||||
case "update":
|
||||
recordAction = "Updated";
|
||||
break;
|
||||
case "delete":
|
||||
recordAction = "Deleted";
|
||||
break;
|
||||
}
|
||||
if (recordType.ToLower().Contains("record"))
|
||||
{
|
||||
var cleanedRecordType = recordType.ToLower().Replace("record", "");
|
||||
cleanedRecordType = $"{char.ToUpper(cleanedRecordType[0])}{cleanedRecordType.Substring(1)} Record";
|
||||
recordType = cleanedRecordType;
|
||||
}
|
||||
else
|
||||
{
|
||||
recordType = $"{char.ToUpper(recordType[0])}{recordType.Substring(1)}";
|
||||
}
|
||||
if (thirdPart == "api")
|
||||
{
|
||||
return $"{recordAction} {recordType} via API";
|
||||
} else
|
||||
{
|
||||
return $"{recordAction} {recordType}";
|
||||
}
|
||||
}
|
||||
return actionType;
|
||||
}
|
||||
public static WebHookPayload FromGenericRecord(GenericRecord genericRecord, string actionType, string userName)
|
||||
{
|
||||
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||
payloadDictionary.Add("user", userName);
|
||||
payloadDictionary.Add("description", genericRecord.Description);
|
||||
payloadDictionary.Add("odometer", genericRecord.Mileage.ToString());
|
||||
payloadDictionary.Add("vehicleId", genericRecord.VehicleId.ToString());
|
||||
payloadDictionary.Add("cost", genericRecord.Cost.ToString("F2"));
|
||||
return new WebHookPayload
|
||||
{
|
||||
Type = actionType,
|
||||
Data = payloadDictionary,
|
||||
VehicleId = genericRecord.VehicleId.ToString(),
|
||||
Username = userName,
|
||||
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {genericRecord.Description}"
|
||||
};
|
||||
}
|
||||
public static WebHookPayload FromGasRecord(GasRecord gasRecord, string actionType, string userName)
|
||||
{
|
||||
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||
payloadDictionary.Add("user", userName);
|
||||
payloadDictionary.Add("odometer", gasRecord.Mileage.ToString());
|
||||
payloadDictionary.Add("fuelconsumed", gasRecord.Gallons.ToString());
|
||||
payloadDictionary.Add("vehicleId", gasRecord.VehicleId.ToString());
|
||||
payloadDictionary.Add("cost", gasRecord.Cost.ToString("F2"));
|
||||
return new WebHookPayload
|
||||
{
|
||||
Type = actionType,
|
||||
Data = payloadDictionary,
|
||||
VehicleId = gasRecord.VehicleId.ToString(),
|
||||
Username = userName,
|
||||
Action = $"{userName} {GetFriendlyActionType(actionType)} Odometer: {gasRecord.Mileage}"
|
||||
};
|
||||
}
|
||||
public static WebHookPayload FromOdometerRecord(OdometerRecord odometerRecord, string actionType, string userName)
|
||||
{
|
||||
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||
payloadDictionary.Add("user", userName);
|
||||
payloadDictionary.Add("initialodometer", odometerRecord.InitialMileage.ToString());
|
||||
payloadDictionary.Add("odometer", odometerRecord.Mileage.ToString());
|
||||
payloadDictionary.Add("vehicleId", odometerRecord.VehicleId.ToString());
|
||||
return new WebHookPayload
|
||||
{
|
||||
Type = actionType,
|
||||
Data = payloadDictionary,
|
||||
VehicleId = odometerRecord.VehicleId.ToString(),
|
||||
Username = userName,
|
||||
Action = $"{userName} {GetFriendlyActionType(actionType)} Odometer: {odometerRecord.Mileage}"
|
||||
};
|
||||
}
|
||||
public static WebHookPayload FromTaxRecord(TaxRecord taxRecord, string actionType, string userName)
|
||||
{
|
||||
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||
payloadDictionary.Add("user", userName);
|
||||
payloadDictionary.Add("description", taxRecord.Description);
|
||||
payloadDictionary.Add("vehicleId", taxRecord.VehicleId.ToString());
|
||||
payloadDictionary.Add("cost", taxRecord.Cost.ToString("F2"));
|
||||
return new WebHookPayload
|
||||
{
|
||||
Type = actionType,
|
||||
Data = payloadDictionary,
|
||||
VehicleId = taxRecord.VehicleId.ToString(),
|
||||
Username = userName,
|
||||
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {taxRecord.Description}"
|
||||
};
|
||||
}
|
||||
public static WebHookPayload FromPlanRecord(PlanRecord planRecord, string actionType, string userName)
|
||||
{
|
||||
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||
payloadDictionary.Add("user", userName);
|
||||
payloadDictionary.Add("description", planRecord.Description);
|
||||
payloadDictionary.Add("vehicleId", planRecord.VehicleId.ToString());
|
||||
payloadDictionary.Add("cost", planRecord.Cost.ToString("F2"));
|
||||
return new WebHookPayload
|
||||
{
|
||||
Type = actionType,
|
||||
Data = payloadDictionary,
|
||||
VehicleId = planRecord.VehicleId.ToString(),
|
||||
Username = userName,
|
||||
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {planRecord.Description}"
|
||||
};
|
||||
}
|
||||
public static WebHookPayload FromReminderRecord(ReminderRecord reminderRecord, string actionType, string userName)
|
||||
{
|
||||
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||
payloadDictionary.Add("user", userName);
|
||||
payloadDictionary.Add("description", reminderRecord.Description);
|
||||
payloadDictionary.Add("vehicleId", reminderRecord.VehicleId.ToString());
|
||||
payloadDictionary.Add("metric", reminderRecord.Metric.ToString());
|
||||
return new WebHookPayload
|
||||
{
|
||||
Type = actionType,
|
||||
Data = payloadDictionary,
|
||||
VehicleId = reminderRecord.VehicleId.ToString(),
|
||||
Username = userName,
|
||||
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {reminderRecord.Description}"
|
||||
};
|
||||
}
|
||||
public static WebHookPayload FromSupplyRecord(SupplyRecord supplyRecord, string actionType, string userName)
|
||||
{
|
||||
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||
payloadDictionary.Add("user", userName);
|
||||
payloadDictionary.Add("description", supplyRecord.Description);
|
||||
payloadDictionary.Add("vehicleId", supplyRecord.VehicleId.ToString());
|
||||
payloadDictionary.Add("cost", supplyRecord.Cost.ToString("F2"));
|
||||
payloadDictionary.Add("quantity", supplyRecord.Quantity.ToString("F2"));
|
||||
return new WebHookPayload
|
||||
{
|
||||
Type = actionType,
|
||||
Data = payloadDictionary,
|
||||
VehicleId = supplyRecord.VehicleId.ToString(),
|
||||
Username = userName,
|
||||
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {supplyRecord.Description}"
|
||||
};
|
||||
}
|
||||
public static WebHookPayload FromNoteRecord(Note noteRecord, string actionType, string userName)
|
||||
{
|
||||
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||
payloadDictionary.Add("user", userName);
|
||||
payloadDictionary.Add("description", noteRecord.Description);
|
||||
payloadDictionary.Add("vehicleId", noteRecord.VehicleId.ToString());
|
||||
return new WebHookPayload
|
||||
{
|
||||
Type = actionType,
|
||||
Data = payloadDictionary,
|
||||
VehicleId = noteRecord.VehicleId.ToString(),
|
||||
Username = userName,
|
||||
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {noteRecord.Description}"
|
||||
};
|
||||
}
|
||||
public static WebHookPayload Generic(string payload, string actionType, string userName, string vehicleId)
|
||||
{
|
||||
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||
payloadDictionary.Add("user", userName);
|
||||
if (!string.IsNullOrWhiteSpace(payload))
|
||||
{
|
||||
payloadDictionary.Add("description", payload);
|
||||
}
|
||||
return new WebHookPayload
|
||||
{
|
||||
Type = actionType,
|
||||
Data = payloadDictionary,
|
||||
VehicleId = string.IsNullOrWhiteSpace(vehicleId) ? "N/A" : vehicleId,
|
||||
Username = userName,
|
||||
Action = string.IsNullOrWhiteSpace(payload) ? $"{userName} {GetFriendlyActionType(actionType)}" : $"{userName} {payload}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Models/Supply/SupplyAvailability.cs
Normal file
11
Models/Supply/SupplyAvailability.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class SupplyAvailability
|
||||
{
|
||||
public bool Missing { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public decimal Required { get; set; }
|
||||
public decimal InStock { get; set; }
|
||||
public bool Insufficient { get { return Required > InStock; } }
|
||||
}
|
||||
}
|
||||
8
Models/Supply/SupplyRequisitionHistory.cs
Normal file
8
Models/Supply/SupplyRequisitionHistory.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class SupplyRequisitionHistory
|
||||
{
|
||||
public string CostInputId { get; set; }
|
||||
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
|
||||
}
|
||||
}
|
||||
8
Models/Supply/SupplyStore.cs
Normal file
8
Models/Supply/SupplyStore.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class SupplyStore
|
||||
{
|
||||
public string Tab { get; set; }
|
||||
public bool AdditionalSupplies { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class SupplyUsageHistory {
|
||||
public int Id { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public string PartNumber { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||
public int CustomMonthInterval { get; set; } = 0;
|
||||
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.ThreeMonths;
|
||||
public int CustomMonthInterval { get; set; } = 0;
|
||||
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
@@ -25,6 +26,7 @@
|
||||
IsRecurring = IsRecurring,
|
||||
RecurringInterval = RecurringInterval,
|
||||
CustomMonthInterval = CustomMonthInterval,
|
||||
CustomMonthIntervalUnit = CustomMonthIntervalUnit,
|
||||
Files = Files,
|
||||
Tags = Tags,
|
||||
ExtraFields = ExtraFields
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
|
||||
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
|
||||
public bool CopySuppliesAttachment { get; set; } = false;
|
||||
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord {
|
||||
Id = Id,
|
||||
|
||||
@@ -13,14 +13,17 @@
|
||||
public bool HideZero { get; set; }
|
||||
public bool UseUKMPG {get;set;}
|
||||
public bool UseThreeDecimalGasCost { get; set; }
|
||||
public bool UseThreeDecimalGasConsumption { get; set; }
|
||||
public bool UseMarkDownOnSavedNotes { get; set; }
|
||||
public bool EnableAutoReminderRefresh { get; set; }
|
||||
public bool EnableAutoOdometerInsert { get; set; }
|
||||
public bool EnableShopSupplies { get; set; }
|
||||
public bool EnableExtraFieldColumns { get; set; }
|
||||
public bool HideSoldVehicles { get; set; }
|
||||
public bool AutomaticDecimalFormat { get; set; }
|
||||
public string PreferredGasUnit { get; set; } = string.Empty;
|
||||
public string PreferredGasMileageUnit { get; set; } = string.Empty;
|
||||
public bool UseUnitForFuelCost { get; set; }
|
||||
public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>();
|
||||
public ReminderUrgencyConfig ReminderUrgencyConfig { get; set; } = new ReminderUrgencyConfig();
|
||||
public string UserNameHash { get; set; }
|
||||
|
||||
62
Program.cs
62
Program.cs
@@ -7,11 +7,14 @@ using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
//Print Messages
|
||||
StaticHelper.InitMessage(builder.Configuration);
|
||||
//Check Migration
|
||||
StaticHelper.CheckMigration(builder.Environment.WebRootPath, builder.Environment.ContentRootPath);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllersWithViews();
|
||||
@@ -65,9 +68,9 @@ builder.Services.AddSingleton<IFileHelper, FileHelper>();
|
||||
builder.Services.AddSingleton<IGasHelper, GasHelper>();
|
||||
builder.Services.AddSingleton<IReminderHelper, ReminderHelper>();
|
||||
builder.Services.AddSingleton<IReportHelper, ReportHelper>();
|
||||
builder.Services.AddSingleton<IMailHelper, MailHelper>();
|
||||
builder.Services.AddSingleton<IConfigHelper, ConfigHelper>();
|
||||
builder.Services.AddSingleton<ITranslationHelper, TranslationHelper>();
|
||||
builder.Services.AddSingleton<IMailHelper, MailHelper>();
|
||||
|
||||
//configure logic
|
||||
builder.Services.AddSingleton<ILoginLogic, LoginLogic>();
|
||||
@@ -75,15 +78,6 @@ builder.Services.AddSingleton<IUserLogic, UserLogic>();
|
||||
builder.Services.AddSingleton<IOdometerLogic, OdometerLogic>();
|
||||
builder.Services.AddSingleton<IVehicleLogic, VehicleLogic>();
|
||||
|
||||
if (!Directory.Exists("data"))
|
||||
{
|
||||
Directory.CreateDirectory("data");
|
||||
}
|
||||
if (!Directory.Exists("config"))
|
||||
{
|
||||
Directory.CreateDirectory("config");
|
||||
}
|
||||
|
||||
//Additional JsonFile
|
||||
builder.Configuration.AddJsonFile(StaticHelper.UserConfigPath, optional: true, reloadOnChange: true);
|
||||
|
||||
@@ -112,13 +106,57 @@ var app = builder.Build();
|
||||
// Configure the HTTP request pipeline.
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(
|
||||
Path.Combine(builder.Environment.ContentRootPath, "data", "images")),
|
||||
RequestPath = "/images",
|
||||
OnPrepareResponse = ctx =>
|
||||
{
|
||||
if (ctx.Context.Request.Path.StartsWithSegments("/images") || ctx.Context.Request.Path.StartsWithSegments("/documents"))
|
||||
if (ctx.Context.Request.Path.StartsWithSegments("/images"))
|
||||
{
|
||||
ctx.Context.Response.Headers.Add("Cache-Control", "no-store");
|
||||
ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
|
||||
if (!ctx.Context.User.Identity.IsAuthenticated)
|
||||
{
|
||||
ctx.Context.Response.Redirect("/Login");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(
|
||||
Path.Combine(builder.Environment.ContentRootPath, "data", "documents")),
|
||||
RequestPath = "/documents",
|
||||
OnPrepareResponse = ctx =>
|
||||
{
|
||||
if (ctx.Context.Request.Path.StartsWithSegments("/documents"))
|
||||
{
|
||||
ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
|
||||
if (!ctx.Context.User.Identity.IsAuthenticated)
|
||||
{
|
||||
ctx.Context.Response.Redirect("/Login");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(
|
||||
Path.Combine(builder.Environment.ContentRootPath, "data", "translations")),
|
||||
RequestPath = "/translations"
|
||||
});
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(
|
||||
Path.Combine(builder.Environment.ContentRootPath, "data", "temp")),
|
||||
RequestPath = "/temp",
|
||||
OnPrepareResponse = ctx =>
|
||||
{
|
||||
if (ctx.Context.Request.Path.StartsWithSegments("/temp"))
|
||||
{
|
||||
ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
|
||||
if (!ctx.Context.User.Identity.IsAuthenticated)
|
||||
{
|
||||
ctx.Context.Response.Redirect("/Login");
|
||||
|
||||
@@ -42,6 +42,7 @@ Read this [Getting Started Guide](https://docs.lubelogger.com/Installation/Getti
|
||||
- [Chart.js](https://github.com/chartjs/Chart.js)
|
||||
- [Drawdown](https://github.com/adamvleggett/drawdown)
|
||||
- [MailKit](https://github.com/jstedfast/MailKit)
|
||||
- [Masonry](https://github.com/desandro/masonry)
|
||||
|
||||
## License
|
||||
MIT
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<div class="col-5 copyable testable">
|
||||
<code>/api/vehicles</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
@@ -42,9 +42,9 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<div class="col-5 copyable testable">
|
||||
<code>/api/vehicle/info</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
@@ -56,7 +56,7 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/adjustedodometer</code>
|
||||
@@ -72,7 +72,7 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/odometerrecords</code>
|
||||
@@ -86,7 +86,7 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/odometerrecords/latest</code>
|
||||
@@ -100,7 +100,7 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
POST
|
||||
<span class="badge bg-primary">POST</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/odometerrecords/add</code>
|
||||
@@ -116,13 +116,52 @@
|
||||
initialOdometer - Initial Odometer reading(optional)<br />
|
||||
odometer - Odometer reading<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge text-bg-warning">PUT</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/odometerrecords/update</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Updates Odometer Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Body(form-data): {<br />
|
||||
Id - Id of Odometer Record<br />
|
||||
date - Date to be entered<br />
|
||||
initialOdometer - Initial Odometer reading<br />
|
||||
odometer - Odometer reading<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge text-bg-danger">DELETE</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/odometerrecords/delete</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Deletes Odometer Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Id - Id of Odometer Record
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/servicerecords</code>
|
||||
@@ -136,7 +175,7 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
POST
|
||||
<span class="badge bg-primary">POST</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/servicerecords/add</code>
|
||||
@@ -153,13 +192,53 @@
|
||||
description - Description<br/>
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge text-bg-warning">PUT</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/servicerecords/update</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Updates Service Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Body(form-data): {<br />
|
||||
Id - Id of Service Record<br />
|
||||
date - Date to be entered<br />
|
||||
odometer - Odometer reading<br />
|
||||
description - Description<br />
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge text-bg-danger">DELETE</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/servicerecords/delete</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Deletes Service Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Id - Id of Service Record
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/repairrecords</code>
|
||||
@@ -173,7 +252,7 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
POST
|
||||
<span class="badge bg-primary">POST</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/repairrecords/add</code>
|
||||
@@ -190,13 +269,53 @@
|
||||
description - Description<br />
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge text-bg-warning">PUT</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/repairrecords/update</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Updates Repair Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Body(form-data): {<br />
|
||||
Id - Id of Repair Record <br />
|
||||
date - Date to be entered<br />
|
||||
odometer - Odometer reading<br />
|
||||
description - Description<br />
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge text-bg-danger">DELETE</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/repairrecords/delete</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Deletes Repair Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Id - Id of Repair Record
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/upgraderecords</code>
|
||||
@@ -210,7 +329,7 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
POST
|
||||
<span class="badge bg-primary">POST</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/upgraderecords/add</code>
|
||||
@@ -227,13 +346,53 @@
|
||||
description - Description<br />
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge text-bg-warning">PUT</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/upgraderecords/update</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Updates Upgrade Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Body(form-data): {<br />
|
||||
Id - Id of Upgrade Record<br />
|
||||
date - Date to be entered<br />
|
||||
odometer - Odometer reading<br />
|
||||
description - Description<br />
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge text-bg-danger">DELETE</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/upgraderecords/delete</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Deletes Upgrade Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Id - Id of Upgrade Record
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/taxrecords</code>
|
||||
@@ -247,7 +406,21 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
POST
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/taxrecords/check</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Updates Outdated Recurring Tax Records
|
||||
</div>
|
||||
<div class="col-3">
|
||||
No Params
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge bg-primary">POST</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/taxrecords/add</code>
|
||||
@@ -263,13 +436,52 @@
|
||||
description - Description<br />
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge text-bg-warning">PUT</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/taxrecords/update</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Updates Tax Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Body(form-data): {<br />
|
||||
Id - Id of Tax Record<br />
|
||||
date - Date to be entered<br />
|
||||
description - Description<br />
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge text-bg-danger">DELETE</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/taxrecords/delete</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Deletes Tax Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Id - Id of Tax Record
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/gasrecords</code>
|
||||
@@ -287,7 +499,7 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
POST
|
||||
<span class="badge bg-primary">POST</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/gasrecords/add</code>
|
||||
@@ -306,13 +518,55 @@
|
||||
isFillToFull(bool) - Filled To Full<br />
|
||||
missedFuelUp(bool) - Missed Fuel Up<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge text-bg-warning">PUT</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/gasrecords/update</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Updates Gas Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Body(form-data): {<br />
|
||||
Id - Id of Gas Record<br />
|
||||
date - Date to be entered<br />
|
||||
odometer - Odometer reading<br />
|
||||
fuelConsumed - Fuel Consumed<br />
|
||||
cost - Cost<br />
|
||||
isFillToFull(bool) - Filled To Full<br />
|
||||
missedFuelUp(bool) - Missed Fuel Up<br />
|
||||
notes - notes(optional)<br />
|
||||
tags - tags separated by space(optional)<br />
|
||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge text-bg-danger">DELETE</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/gasrecords/delete</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Deletes Gas Record
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Id - Id of Gas Record
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/reminders</code>
|
||||
@@ -324,13 +578,43 @@
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable testable">
|
||||
<code>/api/calendar</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Returns reminder calendar in ICS format
|
||||
</div>
|
||||
<div class="col-3">
|
||||
No Params
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
<span class="badge bg-primary">POST</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/documents/upload</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Upload Documents
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Body(form-data): {<br />
|
||||
documents[] - Files to Upload<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<div class="col-5 copyable testable">
|
||||
<code>/api/vehicle/reminders/send</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
@@ -338,14 +622,14 @@
|
||||
</div>
|
||||
<div class="col-3">
|
||||
(must be root user)<br />
|
||||
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue]
|
||||
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue](optional)
|
||||
</div>
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<div class="col-5 copyable testable">
|
||||
<code>/api/makebackup</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
@@ -357,9 +641,9 @@
|
||||
</div>
|
||||
<div class="row api-method">
|
||||
<div class="col-1">
|
||||
GET
|
||||
<span class="badge bg-success">GET</span>
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<div class="col-5 copyable testable">
|
||||
<code>/api/cleanup</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
@@ -367,13 +651,20 @@
|
||||
</div>
|
||||
<div class="col-3">
|
||||
(must be root user)<br />
|
||||
deepClean(bool) - Perform deep clean
|
||||
deepClean(bool) - Perform deep clean(optional)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<script>
|
||||
$('.copyable').on('click', function (e) {
|
||||
copyToClipboard(e.currentTarget);
|
||||
if (e.ctrlKey || e.metaKey){
|
||||
let targetElement = $(e.currentTarget);
|
||||
if (targetElement.hasClass("testable")){
|
||||
window.location = targetElement.text().trim();
|
||||
}
|
||||
} else {
|
||||
copyToClipboard(e.currentTarget);
|
||||
}
|
||||
})
|
||||
function showExtraFieldsInfo(){
|
||||
Swal.fire({
|
||||
@@ -382,4 +673,11 @@
|
||||
icon: "info"
|
||||
});
|
||||
}
|
||||
function showAttachmentsInfo(){
|
||||
Swal.fire({
|
||||
title: "Attaching Files",
|
||||
html: "The Document Upload Endpoint will upload the files and provide a formatted output which you can pass into this method",
|
||||
icon: "info"
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -2,12 +2,12 @@
|
||||
@{
|
||||
ViewData["Title"] = "Admin Panel";
|
||||
}
|
||||
@inject IConfiguration config;
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
bool emailServerIsSetup = true;
|
||||
var mailConfig = config.GetSection("MailConfig").Get<MailConfig>();
|
||||
var userLanguage = config[nameof(UserConfig.UserLanguage)] ?? "en_US";
|
||||
var mailConfig = config.GetMailConfig();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
if (mailConfig is null || string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||
{
|
||||
emailServerIsSetup = false;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var enableAuth = userConfig.EnableAuth;
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
}
|
||||
@model string
|
||||
@{
|
||||
@@ -62,7 +61,7 @@
|
||||
<div class="container">
|
||||
<div class="row mt-2">
|
||||
<div class="d-flex lubelogger-navbar">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="lubelogger-navbar-button">
|
||||
<button type="button" class="btn btn-adaptive" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
|
||||
</div>
|
||||
@@ -147,6 +146,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stickerPrintContainer hideOnPrint">
|
||||
</div>
|
||||
<script>
|
||||
loadGarage();
|
||||
bindWindowResize();
|
||||
|
||||
148
Views/Home/Kiosk.cshtml
Normal file
148
Views/Home/Kiosk.cshtml
Normal file
@@ -0,0 +1,148 @@
|
||||
@{
|
||||
ViewData["Title"] = "Kiosk";
|
||||
}
|
||||
@model KioskViewModel
|
||||
@section Scripts {
|
||||
<script src="~/lib/masonry/masonry.min.js"></script>
|
||||
}
|
||||
<div class="progress" role="progressbar" aria-label="Refresh Progress" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="height: 1px">
|
||||
<div class="progress-bar" style="width: 0%"></div>
|
||||
</div>
|
||||
<div id="kioskContainer" class="container-fluid">
|
||||
</div>
|
||||
<script>
|
||||
let refreshTimer;
|
||||
let exceptionList = [];
|
||||
let subtractAmount = 0;
|
||||
let kioskMode = '@Model.KioskMode';
|
||||
let currentKioskMode = 'Plan';
|
||||
let kioskWakeLock;
|
||||
|
||||
@foreach(int exception in Model.Exclusions)
|
||||
{
|
||||
@:exceptionList.push(@exception);
|
||||
}
|
||||
|
||||
function initKiosk() {
|
||||
$("body > div").removeClass("container");
|
||||
$("body > div").css('height', '100vh');
|
||||
subtractAmount = parseInt($("#kioskContainer").width() * 0.0016); //remove 0.0016% of width every 100 ms which will approximate to one minute.
|
||||
if (subtractAmount < 2) {
|
||||
subtractAmount = 2;
|
||||
}
|
||||
retrieveKioskContent();
|
||||
//acquire wakeLock;
|
||||
try {
|
||||
navigator.wakeLock.request('screen').then((wl) => {
|
||||
kioskWakeLock = wl;
|
||||
});
|
||||
} catch (err) {
|
||||
errorToast('Location Services not Enabled');
|
||||
}
|
||||
}
|
||||
function setAccessToken(accessToken){
|
||||
//use this function to never worry about user session expiring.
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
'Authorization': `Basic ${accessToken}`
|
||||
}
|
||||
});
|
||||
console.log("Access Token for Kiosk Mode Configured!");
|
||||
}
|
||||
function retrieveKioskContent(){
|
||||
clearInterval(refreshTimer);
|
||||
if (kioskMode != 'Cycle'){
|
||||
$.post('/Home/KioskContent', { exclusions: exceptionList, kioskMode: kioskMode }, function (data) {
|
||||
$("#kioskContainer").html(data);
|
||||
$(".kiosk-content").masonry();
|
||||
if ($(".no-data-message").length == 0) {
|
||||
$(".progress-bar").width($("#kioskContainer").width());
|
||||
setTimeout(function () { startTimer() }, 500);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//cycle mode
|
||||
switch (currentKioskMode) {
|
||||
case "Vehicle":
|
||||
currentKioskMode = "Reminder";
|
||||
break;
|
||||
case "Reminder":
|
||||
currentKioskMode = "Plan";
|
||||
break;
|
||||
case "Plan":
|
||||
currentKioskMode = "Vehicle";
|
||||
break;
|
||||
}
|
||||
$.post('/Home/KioskContent', { exclusions: exceptionList, kioskMode: currentKioskMode }, function (data) {
|
||||
$("#kioskContainer").html(data);
|
||||
$(".kiosk-content").masonry();
|
||||
if ($(".no-data-message").length > 0) {
|
||||
//if no data on vehicle page
|
||||
if (currentKioskMode == "Vehicle") {
|
||||
return; //exit
|
||||
} else {
|
||||
retrieveKioskContent(); //skip until we hit a page with content.
|
||||
}
|
||||
} else {
|
||||
$(".progress-bar").width($("#kioskContainer").width());
|
||||
setTimeout(function () { startTimer() }, 500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
function startTimer() {
|
||||
refreshTimer = setInterval(function () {
|
||||
var currentWidth = $(".progress-bar").width();
|
||||
if (currentWidth > 0) {
|
||||
$(".progress-bar").width(currentWidth - subtractAmount);
|
||||
} else {
|
||||
retrieveKioskContent();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
function addVehicleToExceptionList(vehicleId) {
|
||||
Swal.fire({
|
||||
title: "Remove Vehicle from Dashboard?",
|
||||
text: "Removed vehicles can be restored by refreshing the page",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Remove",
|
||||
confirmButtonColor: "#dc3545"
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
exceptionList.push(vehicleId);
|
||||
if (kioskMode == 'Cycle') {
|
||||
//remove the vehicle programmatically.
|
||||
$(`[data-vehicleId=${vehicleId}]`).remove();
|
||||
} else {
|
||||
//force a refresh
|
||||
retrieveKioskContent();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function toggleReminderNote(sender){
|
||||
var reminderNote = $(sender).find('.reminder-note');
|
||||
if (reminderNote.text().trim() != ''){
|
||||
if (reminderNote.hasClass('d-none')) {
|
||||
reminderNote.removeClass('d-none');
|
||||
} else {
|
||||
reminderNote.addClass('d-none');
|
||||
}
|
||||
$(".kiosk-content").masonry();
|
||||
}
|
||||
}
|
||||
function togglePlanDetails(sender) {
|
||||
toggleReminderNote(sender);
|
||||
var planSupplies = $(sender).find('.plan-supplies');
|
||||
if (planSupplies.find('.plan-supply').length > 0) {
|
||||
if (planSupplies.hasClass('d-none')) {
|
||||
planSupplies.removeClass('d-none');
|
||||
} else {
|
||||
planSupplies.addClass('d-none');
|
||||
}
|
||||
$(".kiosk-content").masonry();
|
||||
}
|
||||
}
|
||||
initKiosk();
|
||||
</script>
|
||||
@@ -1,6 +0,0 @@
|
||||
@{
|
||||
ViewData["Title"] = "Privacy Policy";
|
||||
}
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
|
||||
<p>Use this page to detail your site's privacy policy.</p>
|
||||
@@ -10,10 +10,10 @@
|
||||
@if (recordTags.Any())
|
||||
{
|
||||
<div class='row'>
|
||||
<div class="d-flex align-items-center flex-wrap mt-4">
|
||||
<div class="col-12 d-flex align-items-center flex-wrap mt-2 mb-2">
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<span onclick="filterGarage(this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||
<span onclick="filterGarage(this)" class="user-select-none ms-1 me-1 mt-1 mb-1 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||
}
|
||||
<datalist id="tagList">
|
||||
@foreach (string recordTag in recordTags)
|
||||
@@ -24,13 +24,12 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="row gy-3 align-items-stretch vehiclesContainer">
|
||||
<div class="row gy-3 align-items-stretch vehiclesContainer pb-2 @(recordTags.Any() ? "" : "mt-2")">
|
||||
@foreach (VehicleViewModel vehicle in Model)
|
||||
{
|
||||
@if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate)))
|
||||
{
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-4 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="@await Html.PartialAsync("_VehicleExtraFields", vehicle.ExtraFields)" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)">
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-6 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="@await Html.PartialAsync("_VehicleExtraFields", vehicle.ExtraFields)" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)">
|
||||
<div class="card" onclick="viewVehicle(@vehicle.Id)">
|
||||
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" />
|
||||
@if (!string.IsNullOrWhiteSpace(vehicle.SoldDate))
|
||||
@@ -82,10 +81,9 @@
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-4 garage-item-add">
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-6 garage-item-add">
|
||||
<div class="card" onclick="showAddVehicleModal()" style="height:100%;">
|
||||
<img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
126
Views/Home/_Kiosk.cshtml
Normal file
126
Views/Home/_Kiosk.cshtml
Normal file
@@ -0,0 +1,126 @@
|
||||
@using CarCareTracker.Helper
|
||||
@model List<VehicleInfo>
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
|
||||
@foreach (VehicleInfo vehicle in Model)
|
||||
{
|
||||
<div class="col" data-vehicleId="@vehicle.VehicleData.Id">
|
||||
<div class="card" onclick="addVehicleToExceptionList(@vehicle.VehicleData.Id)">
|
||||
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||
<h5 class="card-title">@($"{vehicle.VehicleData.Year} {vehicle.VehicleData.Make} {vehicle.VehicleData.Model} ({StaticHelper.GetVehicleIdentifier(vehicle.VehicleData)})")</h5>
|
||||
<div class="row">
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.ServiceRecordCount</p>
|
||||
<p class="lead text-truncate">@translator.Translate(userLanguage, "Service")</p>
|
||||
<p class="lead text-truncate">@vehicle.ServiceRecordCost.ToString("C0")</p>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.RepairRecordCount</p>
|
||||
<p class="lead text-truncate">@translator.Translate(userLanguage, "Repairs")</p>
|
||||
<p class="lead text-truncate">@vehicle.RepairRecordCost.ToString("C0")</p>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.UpgradeRecordCount</p>
|
||||
<p class="lead text-truncate">@translator.Translate(userLanguage, "Upgrades")</p>
|
||||
<p class="lead text-truncate">@vehicle.UpgradeRecordCost.ToString("C0")</p>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.GasRecordCount</p>
|
||||
<p class="lead text-truncate">@translator.Translate(userLanguage, "Fuel")</p>
|
||||
<p class="lead text-truncate">@vehicle.GasRecordCost.ToString("C0")</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (vehicle.PastDueReminderCount + vehicle.VeryUrgentReminderCount + vehicle.UrgentReminderCount + vehicle.NotUrgentReminderCount > 0)
|
||||
{
|
||||
<hr style="margin:0px;" />
|
||||
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||
<h5 class="card-title">@translator.Translate(userLanguage, "Reminders")</h5>
|
||||
<div class="row">
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.PastDueReminderCount</p>
|
||||
<p class="lead text-wrap">@translator.Translate(userLanguage, "Past Due")</p>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.VeryUrgentReminderCount</p>
|
||||
<p class="lead text-wrap">@translator.Translate(userLanguage, "Very Urgent")</p>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.UrgentReminderCount</p>
|
||||
<p class="lead text-wrap">@translator.Translate(userLanguage, "Urgent")</p>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.NotUrgentReminderCount</p>
|
||||
<p class="lead text-wrap">@translator.Translate(userLanguage, "Not Urgent")</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (vehicle.NextReminder != null)
|
||||
{
|
||||
<hr style="margin:0px;" />
|
||||
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||
<h5 class="card-title">@translator.Translate(userLanguage, "Upcoming Reminder")</h5>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p class="display-7">@vehicle.NextReminder.Description</p>
|
||||
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetTitleCaseReminderUrgency(vehicle.NextReminder.Urgency))</p>
|
||||
<div class="row">
|
||||
@if (vehicle.NextReminder.Metric == "Date" || vehicle.NextReminder.Metric == "Both")
|
||||
{
|
||||
<div class="col-6"><i class='bi bi-calendar-event me-2'></i>@vehicle.NextReminder.DueDate</div>
|
||||
}
|
||||
@if (vehicle.NextReminder.Metric == "Odometer" || vehicle.NextReminder.Metric == "Both")
|
||||
{
|
||||
<div class="col-6"><i class='bi bi-speedometer me-2'></i>@vehicle.NextReminder.DueOdometer</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (vehicle.PlanRecordBackLogCount + vehicle.PlanRecordInProgressCount + vehicle.PlanRecordTestingCount + vehicle.PlanRecordBackLogCount > 0)
|
||||
{
|
||||
<hr style="margin:0px;" />
|
||||
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||
<h5 class="card-title">@translator.Translate(userLanguage, "Plans")</h5>
|
||||
<div class="row">
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.PlanRecordBackLogCount</p>
|
||||
<p class="lead text-wrap">@translator.Translate(userLanguage, "Planned")</p>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.PlanRecordInProgressCount</p>
|
||||
<p class="lead text-wrap">@translator.Translate(userLanguage, "Doing")</p>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.PlanRecordTestingCount</p>
|
||||
<p class="lead text-wrap">@translator.Translate(userLanguage, "Testing")</p>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<p class="display-7">@vehicle.PlanRecordDoneCount</p>
|
||||
<p class="lead text-wrap">@translator.Translate(userLanguage, "Done")</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row no-data-message">
|
||||
<div class="col">
|
||||
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
86
Views/Home/_KioskPlan.cshtml
Normal file
86
Views/Home/_KioskPlan.cshtml
Normal file
@@ -0,0 +1,86 @@
|
||||
@using CarCareTracker.Helper
|
||||
@model List<PlanRecord>
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
|
||||
@foreach (PlanRecord plan in Model)
|
||||
{
|
||||
<div class="col" onclick="togglePlanDetails(this)">
|
||||
<div class="card @StaticHelper.GetPlanRecordColor(plan.Priority)">
|
||||
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||
<h5 class="card-title">@plan.Description</h5>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p class="display-7 d-none reminder-note" style="white-space: pre-wrap">@plan.Notes</p>
|
||||
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetPlanRecordProgress(plan.Progress))</p>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
@if (plan.ImportMode == ImportMode.ServiceRecord)
|
||||
{
|
||||
<span class="lead">@translator.Translate(userLanguage, "Service")</span>
|
||||
}
|
||||
else if (plan.ImportMode == ImportMode.UpgradeRecord)
|
||||
{
|
||||
<span class="lead">@translator.Translate(userLanguage, "Repairs")</span>
|
||||
}
|
||||
else if (plan.ImportMode == ImportMode.RepairRecord)
|
||||
{
|
||||
<span class="lead">@translator.Translate(userLanguage, "Upgrades")</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (plan.RequisitionHistory.Any())
|
||||
{
|
||||
<ul class="list-group list-group-flush plan-supplies d-none">
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
@translator.Translate(userLanguage, "Part Number")
|
||||
</div>
|
||||
<div class="col-4">
|
||||
@translator.Translate(userLanguage, "Description")
|
||||
</div>
|
||||
<div class="col-4">
|
||||
@translator.Translate(userLanguage, "Quantity")
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@foreach (SupplyUsageHistory supply in plan.RequisitionHistory)
|
||||
{
|
||||
<li class="list-group-item plan-supply">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
@supply.PartNumber
|
||||
</div>
|
||||
<div class="col-4">
|
||||
@supply.Description
|
||||
</div>
|
||||
<div class="col-4">
|
||||
@supply.Quantity
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} else
|
||||
{
|
||||
<div class="row no-data-message">
|
||||
<div class="col">
|
||||
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
47
Views/Home/_KioskReminder.cshtml
Normal file
47
Views/Home/_KioskReminder.cshtml
Normal file
@@ -0,0 +1,47 @@
|
||||
@using CarCareTracker.Helper
|
||||
@model List<ReminderRecordViewModel>
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
|
||||
@foreach (ReminderRecordViewModel reminder in Model)
|
||||
{
|
||||
<div class="col" onclick="toggleReminderNote(this)">
|
||||
<div class="card @StaticHelper.GetReminderUrgencyColor(reminder.Urgency)">
|
||||
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||
<h5 class="card-title">@reminder.Description</h5>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p class="display-7 d-none reminder-note" style="white-space: pre-wrap">@reminder.Notes</p>
|
||||
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency))</p>
|
||||
<div class="row">
|
||||
@if (reminder.Metric == ReminderMetric.Date || reminder.Metric == ReminderMetric.Both)
|
||||
{
|
||||
<div class="col-6"><i class='bi bi-calendar-event me-2'></i>@reminder.Date.ToShortDateString()</div>
|
||||
}
|
||||
@if (reminder.Metric == ReminderMetric.Odometer || reminder.Metric == ReminderMetric.Both)
|
||||
{
|
||||
<div class="col-6"><i class='bi bi-speedometer me-2'></i>@reminder.Mileage</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row no-data-message">
|
||||
<div class="col">
|
||||
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -37,13 +37,29 @@
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useDescending" checked="@Model.UserConfig.UseDescending">
|
||||
<label class="form-check-label" for="useDescending">@translator.Translate(userLanguage, "Sort lists in Descending Order(Newest to Oldest)")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideZero" checked="@Model.UserConfig.HideZero">
|
||||
<label class="form-check-label" for="hideZero">@translator.Translate(userLanguage, "Replace $0.00 Costs with ---")</label>
|
||||
<div>
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideZero" checked="@Model.UserConfig.HideZero">
|
||||
<label class="form-check-label" for="hideZero">@translator.Translate(userLanguage, "Replace $0.00 Costs with ---")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="automaticDecimalFormat" checked="@Model.UserConfig.AutomaticDecimalFormat">
|
||||
<label class="form-check-label" for="automaticDecimalFormat">@translator.Translate(userLanguage, "Automatically Format Decimal Inputs")</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimal" checked="@Model.UserConfig.UseThreeDecimalGasCost">
|
||||
<label class="form-check-label" for="useThreeDecimal">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Cost")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useUnitForFuelCost" checked="@Model.UserConfig.UseUnitForFuelCost">
|
||||
<label class="form-check-label" for="useUnitForFuelCost">@translator.Translate(userLanguage, "Default to Fuel Unit Cost")</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimal" checked="@Model.UserConfig.UseThreeDecimalGasCost">
|
||||
<label class="form-check-label" for="useThreeDecimal">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Cost")</label>
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimalGasConsumption" checked="@Model.UserConfig.UseThreeDecimalGasConsumption">
|
||||
<label class="form-check-label" for="useThreeDecimalGasConsumption">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Consumption")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useMarkDownOnSavedNotes" checked="@Model.UserConfig.UseMarkDownOnSavedNotes">
|
||||
@@ -61,13 +77,15 @@
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableExtraFieldColumns" checked="@Model.UserConfig.EnableExtraFieldColumns">
|
||||
<label class="form-check-label" for="enableExtraFieldColumns">@translator.Translate(userLanguage, "Show Extra Field Columns")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Enabling this may cause performance issues")</small></label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideSoldVehicles" checked="@Model.UserConfig.HideSoldVehicles">
|
||||
<label class="form-check-label" for="hideSoldVehicles">@translator.Translate(userLanguage, "Hide Sold Vehicles")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch @(User.IsInRole(nameof(UserData.IsRootUser)) ? "" : "d-none")">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableShopSupplies" checked="@Model.UserConfig.EnableShopSupplies">
|
||||
<label class="form-check-label" for="enableShopSupplies">@translator.Translate(userLanguage, "Shop Supplies")</label>
|
||||
<div>
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideSoldVehicles" checked="@Model.UserConfig.HideSoldVehicles">
|
||||
<label class="form-check-label" for="hideSoldVehicles">@translator.Translate(userLanguage, "Hide Sold Vehicles")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch form-check-inline @(User.IsInRole(nameof(UserData.IsRootUser)) ? "" : "d-none")">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableShopSupplies" checked="@Model.UserConfig.EnableShopSupplies">
|
||||
<label class="form-check-label" for="enableShopSupplies">@translator.Translate(userLanguage, "Shop Supplies")</label>
|
||||
</div>
|
||||
</div>
|
||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
@@ -308,6 +326,7 @@
|
||||
<li class="list-group-item">Chart.js</li>
|
||||
<li class="list-group-item">Drawdown</li>
|
||||
<li class="list-group-item">MailKit</li>
|
||||
<li class="list-group-item">Masonry</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -330,6 +349,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" data-bs-focus="false" id="customWidgetModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="customWidgetModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" data-bs-focus="false" id="tabReorderModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="tabReorderModalContent">
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@inject IConfiguration serverConfig;
|
||||
@model Dictionary<string, string>
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
bool showDelete = bool.Parse(serverConfig["LUBELOGGER_TRANSLATOR"] ?? "false");
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="translationEditorModalLabel">@translator.Translate(userLanguage, "Translation Editor")</h5>
|
||||
@@ -15,8 +17,15 @@
|
||||
<div class="form-group" style="max-height:50vh; overflow-x:hidden; overflow-y:scroll;">
|
||||
@foreach(var translation in Model)
|
||||
{
|
||||
<div class="row translation-keyvalue mb-2">
|
||||
<div class="col-md-6 col-12 translation-key">@translation.Key.Replace("_", " ")</div>
|
||||
<div class="row translation-keyvalue mb-2 align-items-center">
|
||||
@if (showDelete)
|
||||
{
|
||||
<div class="col-md-1 col-1 translation-delete"><button onclick="deleteTranslationKey(this)" class="btn text-danger btn-sm"><i class="bi bi-x-lg"></i></button></div>
|
||||
<div class="col-md-5 col-11 translation-key">@translation.Key.Replace("_", " ")</div>
|
||||
} else
|
||||
{
|
||||
<div class="col-md-6 col-12 translation-key">@translation.Key.Replace("_", " ")</div>
|
||||
}
|
||||
<div class="col-md-6 col-12 translation-value">
|
||||
<textarea style="height:100%;width:98%;" class="form-control" placeholder="@translation.Value">@translation.Value</textarea>
|
||||
</div>
|
||||
|
||||
26
Views/Home/_VehicleSelector.cshtml
Normal file
26
Views/Home/_VehicleSelector.cshtml
Normal file
@@ -0,0 +1,26 @@
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@using CarCareTracker.Helper
|
||||
@model List<Vehicle>
|
||||
|
||||
@{
|
||||
var userLanguage = config.GetUserConfig(User).UserLanguage;
|
||||
}
|
||||
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div id="vehicleSelector">
|
||||
<ul class="list-group">
|
||||
@foreach (Vehicle vehicle in Model)
|
||||
{
|
||||
<li class="list-group-item text-start">
|
||||
<input class="form-check-input" type="checkbox" value="@vehicle.Id" id="vehicleCheck_@vehicle.Id">
|
||||
<label class="form-check-label stretched-link" for="vehicleCheck_@vehicle.Id">@($"{vehicle.Year} {vehicle.Make} {vehicle.Model} ({StaticHelper.GetVehicleIdentifier(vehicle)})")</label>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
} else
|
||||
{
|
||||
<div id="vehicleSelector"><span class="lead">@translator.Translate(userLanguage, "No Vehicles Available")</span></div>
|
||||
}
|
||||
28
Views/Home/_WidgetEditor.cshtml
Normal file
28
Views/Home/_WidgetEditor.cshtml
Normal file
@@ -0,0 +1,28 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model string
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="widgetEditorModalLabel">@translator.Translate(userLanguage, "Custom Widgets Editor")</h5>
|
||||
<button type="button" class="btn-close" onclick="hideCustomWidgets()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" onkeydown="handleEnter(this)">
|
||||
<form class="form-inline">
|
||||
<div class="form-group" style="height:50vh; overflow-x:hidden; overflow-y:auto;">
|
||||
<div class="row">
|
||||
<div class="col-12" style="height:48vh;">
|
||||
<textarea id="widgetEditor" style="width:100%; height:100%;">@Model</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger me-auto" onclick="deleteCustomWidgets()">@translator.Translate(userLanguage, "Delete")</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideCustomWidgets()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveCustomWidgets()">@translator.Translate(userLanguage, "Save")</button>
|
||||
</div>
|
||||
@@ -2,7 +2,6 @@
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@{
|
||||
@@ -14,7 +13,7 @@
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div>
|
||||
<div style="max-width:204px;">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||
<input type="text" id="inputUserName" class="form-control">
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
@inject ITranslationHelper translator
|
||||
@model string
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
var registrationDisabled = config.GetServerDisabledRegistration();
|
||||
var openIdConfigName = config.GetOpenIDConfig().Name;
|
||||
@@ -17,7 +16,7 @@
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div>
|
||||
<div style="max-width:204px;">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||
<input type="text" id="inputUserName" class="form-control">
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
var openRegistrationEnabled = config.GetServerOpenRegistration();
|
||||
}
|
||||
@model string
|
||||
@{
|
||||
@@ -15,10 +15,22 @@
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div>
|
||||
<div style="max-width:204px;">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
@if (openRegistrationEnabled)
|
||||
{
|
||||
<div class="input-group">
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="sendOpenIdRegistrationToken()"><i class="bi bi-send"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||
@@ -47,4 +59,14 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
function sendOpenIdRegistrationToken(){
|
||||
var userEmail = decodeHTMLEntities('@Model');
|
||||
$.post('/Login/SendRegistrationToken', { emailAddress: userEmail }, function (data) {
|
||||
if (data.success) {
|
||||
successToast(data.message);
|
||||
} else {
|
||||
errorToast(data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -2,8 +2,8 @@
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
var openRegistrationEnabled = config.GetServerOpenRegistration();
|
||||
}
|
||||
@{
|
||||
ViewData["Title"] = "Register";
|
||||
@@ -14,13 +14,22 @@
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div>
|
||||
<div style="max-width:204px;">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
@if (openRegistrationEnabled) {
|
||||
<div class="input-group">
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="sendRegistrationToken()"><i class="bi bi-send"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Email Address")</label>
|
||||
<label for="inputEmail">@translator.Translate(userLanguage, "Email Address")</label>
|
||||
<input type="text" id="inputEmail" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@{
|
||||
@@ -14,7 +13,7 @@
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div>
|
||||
<div style="max-width:204px;">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
@using CarCareTracker.Helper
|
||||
@{
|
||||
@{
|
||||
ViewData["Title"] = "Database Migration";
|
||||
}
|
||||
@inject IConfiguration config;
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userLanguage = config[nameof(UserConfig.UserLanguage)] ?? "en_US";
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@using CarCareTracker.Helper
|
||||
@model AdminViewModel
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
@@ -21,9 +21,9 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">Instructions</li>
|
||||
<li class="list-group-item">Use this tool to migrate data between LiteDB and Postgres</li>
|
||||
<li class="list-group-item">Note that it is recommended that the Postgres DB is empty when importing from LiteDB to prevent primary key errors.</li>
|
||||
<li class="list-group-item">@translator.Translate(userLanguage, "Instructions")</li>
|
||||
<li class="list-group-item">@translator.Translate(userLanguage, "Use this tool to migrate data between LiteDB and Postgres")</li>
|
||||
<li class="list-group-item">@translator.Translate(userLanguage, "Note that it is recommended that the Postgres DB is empty when importing from LiteDB to prevent primary key errors.")</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@using CarCareTracker.Helper
|
||||
@using System.Globalization
|
||||
<!DOCTYPE html>
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@@ -10,9 +11,10 @@
|
||||
var useMPG = userConfig.UseMPG;
|
||||
var useMarkDown = userConfig.UseMarkDownOnSavedNotes;
|
||||
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
|
||||
var shortDatePattern = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
|
||||
var firstDayOfWeek = (int)System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
|
||||
var numberFormat = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
|
||||
var automaticDecimalFormat = userConfig.AutomaticDecimalFormat;
|
||||
var shortDatePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
|
||||
var firstDayOfWeek = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
|
||||
var numberFormat = CultureInfo.CurrentCulture.NumberFormat;
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
shortDatePattern = shortDatePattern.ToLower();
|
||||
if (!shortDatePattern.Contains("dd"))
|
||||
@@ -32,6 +34,7 @@
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<title>@ViewData["Title"] - LubeLogger</title>
|
||||
<link rel="icon" type="image/x-icon" href="~/favicon.ico">
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap-icons.css" />
|
||||
<link rel="stylesheet" href="~/lib/bootstrap-datepicker/css/bootstrap-datepicker.min.css" />
|
||||
@@ -90,6 +93,42 @@
|
||||
input = input.replace(".", decimalSeparator);
|
||||
return input;
|
||||
}
|
||||
function fixDecimalInput(input, numOfDecimals) {
|
||||
if ("@automaticDecimalFormat" == "True") {
|
||||
//get the decimal separator.
|
||||
var decimalSeparator = decodeHTMLEntities("@numberFormat.NumberDecimalSeparator");
|
||||
var inputText = $(input).val().trim();
|
||||
inputText = inputText.replace(decimalSeparator, '');
|
||||
inputText = +inputText;
|
||||
if (isNaN(inputText)){
|
||||
return;
|
||||
}
|
||||
inputText = inputText.toString();
|
||||
if (inputText == '') {
|
||||
return;
|
||||
};
|
||||
//check number of decimals.
|
||||
if (inputText.length <= numOfDecimals) {
|
||||
//less than number of decimals, assume everything is behind the decimal.
|
||||
inputText = `0${decimalSeparator}${inputText}`; //add leading zero.
|
||||
$(input).val(inputText);
|
||||
} else {
|
||||
//check if leading zero
|
||||
var charToSlice = numOfDecimals * -1;
|
||||
var charsBehindDecimal = inputText.slice(charToSlice);
|
||||
var charsFrontDecimal = inputText.substr(0, inputText.length - numOfDecimals);
|
||||
inputText = `${charsFrontDecimal}${decimalSeparator}${charsBehindDecimal}`;
|
||||
$(input).val(inputText);
|
||||
}
|
||||
}
|
||||
}
|
||||
function interceptDecimalKeys(event) {
|
||||
if ("@automaticDecimalFormat" == "True") {
|
||||
if (event.which == 190 || event.which == 188) {
|
||||
event.preventDefault(); //intercept keys.
|
||||
}
|
||||
}
|
||||
}
|
||||
function genericErrorMessage(){
|
||||
return decodeHTMLEntities('@translator.Translate(userLanguage, "An error has occurred, please try again later")');
|
||||
}
|
||||
|
||||
@@ -158,6 +158,8 @@
|
||||
<div class="modal-content" id="inputSuppliesModalContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stickerPrintContainer hideOnPrint">
|
||||
</div>
|
||||
<script>
|
||||
function GetVehicleId() {
|
||||
return {
|
||||
@@ -173,4 +175,5 @@
|
||||
return { tab: "@userConfig.DefaultTab" };
|
||||
}
|
||||
bindWindowResize();
|
||||
checkRecurringTaxes();
|
||||
</script>
|
||||
9
Views/Vehicle/_AttachmentColumn.cshtml
Normal file
9
Views/Vehicle/_AttachmentColumn.cshtml
Normal file
@@ -0,0 +1,9 @@
|
||||
@model List<UploadedFiles>
|
||||
@if (Model.Any()){
|
||||
<span class="position-relative">
|
||||
<i class="bi bi-paperclip"></i>
|
||||
<span class="position-absolute attachment-badge-xs start-100 translate-middle badge rounded-pill bg-secondary">
|
||||
@Model.Count()
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
@@ -42,6 +42,7 @@
|
||||
function deleteCollaborator(userId, vehicleId) {
|
||||
$.post('/Vehicle/DeleteCollaboratorFromVehicle', {userId: userId, vehicleId: vehicleId}, function(data){
|
||||
if (data) {
|
||||
successToast('Collaborator Removed');
|
||||
refreshCollaborators();
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
@@ -68,6 +69,7 @@
|
||||
var vehicleId = GetVehicleId().vehicleId;
|
||||
$.post('/Vehicle/AddCollaboratorsToVehicle', { username: result.value.userName, vehicleId: vehicleId }, function (data) {
|
||||
if (data.success) {
|
||||
successToast(data.message);
|
||||
refreshCollaborators();
|
||||
} else {
|
||||
errorToast(data.message)
|
||||
|
||||
@@ -43,11 +43,8 @@
|
||||
</div>
|
||||
}
|
||||
<label for="collisionRecordCost">@translator.Translate(userLanguage, "Cost")</label>
|
||||
<input type="text" inputmode="decimal" id="collisionRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the repair")" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "RepairRecord")
|
||||
}
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="collisionRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the repair")" value="@(isNew ? "" : Model.Cost)">
|
||||
@await Html.PartialAsync("_SupplyStore", new SupplyStore { Tab = "RepairRecord", AdditionalSupplies = Model.RequisitionHistory.Any() })
|
||||
<label for="collisionRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="collisionRecordTag">
|
||||
@foreach (string tag in Model.Tags)
|
||||
@@ -126,7 +123,7 @@
|
||||
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle(true)">@translator.Translate(userLanguage, "Edit Repair Record")</button>
|
||||
}
|
||||
</div>
|
||||
@await Html.PartialAsync("_SupplyRequisitionHistory", Model.RequisitionHistory)
|
||||
@await Html.PartialAsync("_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "collisionRecordCost" })
|
||||
<script>
|
||||
var uploadedFiles = [];
|
||||
var selectedSupplies = [];
|
||||
|
||||
@@ -46,6 +46,13 @@
|
||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="searchTableRows('accident-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<div class="list-group-item">
|
||||
<input class="btn-check" type="checkbox" id="chkSelectMode">
|
||||
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
|
||||
</div>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||
<li class="dropdown-item">
|
||||
<div class="list-group-item">
|
||||
@@ -71,6 +78,12 @@
|
||||
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
|
||||
</div>
|
||||
</li>
|
||||
<li class="dropdown-item">
|
||||
<div class="list-group-item">
|
||||
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Attachment">
|
||||
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
|
||||
</div>
|
||||
</li>
|
||||
<li class="dropdown-item">
|
||||
<div class="list-group-item">
|
||||
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Notes" checked>
|
||||
@@ -101,7 +114,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@@ -111,6 +124,7 @@
|
||||
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
|
||||
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('accident-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
|
||||
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
{
|
||||
@@ -125,8 +139,9 @@
|
||||
<td class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(collisionRecord.Date)">@collisionRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(collisionRecord.Mileage == default ? "---" : collisionRecord.Mileage.ToString())</td>
|
||||
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@collisionRecord.Description</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(collisionRecord.Cost, hideZero))</td>
|
||||
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", collisionRecord.Files)</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
{
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
|
||||
@@ -134,7 +149,7 @@
|
||||
var extraFieldValue = collisionRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
|
||||
{
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -146,6 +161,13 @@
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,20 +181,25 @@
|
||||
</div>
|
||||
|
||||
<ul class="table-context-menu dropdown-menu" style="display:none;">
|
||||
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
|
||||
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
|
||||
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
|
||||
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
|
||||
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
|
||||
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
|
||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Move To")</h6></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'RepairRecord', 'ServiceRecord')">@translator.Translate(userLanguage, "Service Records")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'RepairRecord', 'UpgradeRecord')">@translator.Translate(userLanguage, "Upgrades")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'RepairRecord', 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Service Records")</span><i class="bi bi-card-checklist"></i></div></a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'RepairRecord', 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Upgrades")</span><i class="bi bi-wrench-adjustable"></i></div></a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
||||
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
|
||||
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</a></li>
|
||||
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Statistics")</span><i class="bi bi-graph-up"></i></div></a></li>
|
||||
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
||||
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
|
||||
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
|
||||
</ul>
|
||||
@if (userColumnPreferences.Any())
|
||||
{
|
||||
|
||||
96
Views/Vehicle/_CostDistanceTableReport.cshtml
Normal file
96
Views/Vehicle/_CostDistanceTableReport.cshtml
Normal file
@@ -0,0 +1,96 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model CostDistanceTableForVehicle
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var hideZero = userConfig.HideZero;
|
||||
var years = Model.CostData.Select(x => x.Year).OrderByDescending(x => x).Distinct();
|
||||
var months = Model.CostData.OrderBy(x => x.MonthId).Select(x => x.MonthName).Distinct();
|
||||
}
|
||||
@if (Model.CostData.Any())
|
||||
{
|
||||
<div>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(translator.Translate(userLanguage, "Vehicle Monthly Cost Breakdown"))</h5>
|
||||
<button type="button" class="btn-close" onclick="hideDataTable()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-12" style="overflow-x:auto;">
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" style="cursor:pointer;" onclick="toggleBarChartTableData()" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@(translator.Translate(userLanguage, "Year"))</th>
|
||||
@foreach(int year in years){
|
||||
if (year != default){
|
||||
<th scope="col" style="cursor:pointer;" onclick="toggleBarChartTableData()" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@year</th>
|
||||
}
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(string month in months){
|
||||
<tr class="d-flex">
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@month</td>
|
||||
@foreach(int year in years){
|
||||
if (year != default){
|
||||
{
|
||||
var dataToDisplay = Model.CostData.Where(x => x.Year == year && x.MonthName == month).FirstOrDefault();
|
||||
if (dataToDisplay != null && dataToDisplay != default)
|
||||
{
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(dataToDisplay.Cost.ToString("C2"), hideZero))</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@(dataToDisplay.DistanceTraveled != default ? $"{dataToDisplay.DistanceTraveled.ToString("N0")} {Model.DistanceUnit}" : "---")</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@(StaticHelper.HideZeroCost(dataToDisplay.CostPerDistanceTraveled.ToString("C2"), hideZero, $"/{Model.DistanceUnit}"))</td>
|
||||
} else {
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero))</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@("---")</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@($"{StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero, $"/{Model.DistanceUnit}")}")</td>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="d-flex fw-bold">
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@(translator.Translate(userLanguage, "Total"))</td>
|
||||
@foreach (int year in years)
|
||||
{
|
||||
if (year != default)
|
||||
{
|
||||
{
|
||||
var yearDataToDisplay = Model.CostData.Where(x => x.Year == year);
|
||||
if (yearDataToDisplay != null && yearDataToDisplay != default)
|
||||
{
|
||||
var distanceTraveled = yearDataToDisplay.Sum(x => x.DistanceTraveled);
|
||||
var costAccrued = yearDataToDisplay.Sum(x => x.Cost);
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(costAccrued.ToString("C2"), hideZero))</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@(distanceTraveled != default ? $"{distanceTraveled.ToString("N0")} {Model.DistanceUnit}" : "---")</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@(StaticHelper.HideZeroCost(distanceTraveled != default && costAccrued != default ? (costAccrued / distanceTraveled).ToString("C2") : 0M.ToString("C2"), hideZero, $"/{Model.DistanceUnit}"))</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero))</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@("---")</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@($"{StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero)}")</td>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-center">
|
||||
<h4>@translator.Translate(userLanguage, "No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.")</h4>
|
||||
</div>
|
||||
}
|
||||
@@ -16,28 +16,33 @@
|
||||
new Chart($("#pie-chart"), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: [decodeHTMLEntities('@translator.Translate(userLanguage, "Service Records")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Repairs")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Upgrades")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Tax")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Fuel")')],
|
||||
labels: [
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Service Records")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Repairs")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Upgrades")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Fuel")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Tax")')
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses by Type")'),
|
||||
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ff6361", "#ffa600"],
|
||||
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ffa600", "#ff6361" ],
|
||||
data: [
|
||||
globalParseFloat('@Model.ServiceRecordSum'),
|
||||
globalParseFloat('@Model.CollisionRecordSum'),
|
||||
globalParseFloat('@Model.UpgradeRecordSum'),
|
||||
globalParseFloat('@Model.TaxRecordSum'),
|
||||
globalParseFloat('@Model.GasRecordSum')
|
||||
globalParseFloat('@Model.GasRecordSum'),
|
||||
globalParseFloat('@Model.TaxRecordSum')
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
onClick: (e) => {
|
||||
showDataTable();
|
||||
onClick: (e, a, c) => {
|
||||
showDataTable(a);
|
||||
},
|
||||
onHover: (e, a, c) => {
|
||||
a.length > 0 ? c.canvas.style.cursor = 'pointer' : c.canvas.style.cursor = 'default';
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
|
||||
@@ -21,49 +21,51 @@
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Type")</th>
|
||||
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Cost Per Day")</th>
|
||||
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, Model.DistanceUnit)</th>
|
||||
<th scope="col" style="cursor:pointer;" onclick="toggleCostTableHint()" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Cost Per Day")<span class="cost-table-hint d-none">@($"({Model.NumberOfDays.ToString("N0")})")</span></th>
|
||||
<th scope="col" style="cursor:pointer;" onclick="toggleCostTableHint()" class="col-3 flex-grow-1">@translator.Translate(userLanguage, Model.DistanceUnit)<span class="cost-table-hint d-none">@($"({Model.TotalDistance.ToString("N0")})")</span></th>
|
||||
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Total")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="d-flex">
|
||||
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Service Records")</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.ServiceRecordPerDay == default ? "---" : Model.ServiceRecordPerDay.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.ServiceRecordPerMile == default ? "---" :Model.ServiceRecordPerMile.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.ServiceRecordSum == default ? "---" :Model.ServiceRecordSum.ToString("C2"))</td>
|
||||
</tr>
|
||||
<tr class="d-flex">
|
||||
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Service Records")</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.ServiceRecordPerDay.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.ServiceRecordPerMile.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.ServiceRecordSum.ToString("C2"), hideZero))</td>
|
||||
</tr>
|
||||
<tr class="d-flex">
|
||||
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Repairs")</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.CollisionRecordPerDay == default ? "---" :Model.CollisionRecordPerDay.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.CollisionRecordPerMile == default ? "---" :Model.CollisionRecordPerMile.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.CollisionRecordSum == default ? "---" :Model.CollisionRecordSum.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.CollisionRecordPerDay.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.CollisionRecordPerMile.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.CollisionRecordSum.ToString("C2"), hideZero))</td>
|
||||
</tr>
|
||||
<tr class="d-flex">
|
||||
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Upgrades")</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.UpgradeRecordPerDay == default ? "---" :Model.UpgradeRecordPerDay.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.UpgradeRecordPerMile == default ? "---" :Model.UpgradeRecordPerMile.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.UpgradeRecordSum == default ? "---" :Model.UpgradeRecordSum.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.UpgradeRecordPerDay.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.UpgradeRecordPerMile.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.UpgradeRecordSum.ToString("C2"), hideZero))</td>
|
||||
</tr>
|
||||
<tr class="d-flex">
|
||||
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Fuel")</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.GasRecordPerDay == default ? "---" :Model.GasRecordPerDay.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.GasRecordPerMile == default ? "---" :Model.GasRecordPerMile.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.GasRecordSum == default ? "---" :Model.GasRecordSum.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.GasRecordPerDay.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.GasRecordPerMile.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.GasRecordSum.ToString("C2"), hideZero))</td>
|
||||
</tr>
|
||||
<tr class="d-flex">
|
||||
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Taxes")</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.TaxRecordPerDay == default ? "---" :Model.TaxRecordPerDay.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.TaxRecordPerMile == default ? "---" : Model.TaxRecordPerMile.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.TaxRecordSum == default ? "---" :Model.TaxRecordSum.ToString("C2"))</td>
|
||||
</tr>
|
||||
<tr class="d-flex">
|
||||
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Total")</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.TotalPerDay == default ? "---" : Model.TotalPerDay.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.TotalPerMile == default ? "---" : Model.TotalPerMile.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(hideZero && Model.TotalCost == default ? "---" : Model.TotalCost.ToString("C2"))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TaxRecordPerDay.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TaxRecordPerMile.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TaxRecordSum.ToString("C2"), hideZero))</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="d-flex fw-bold">
|
||||
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Total")</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TotalPerDay.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TotalPerMile.ToString("C2"), hideZero))</td>
|
||||
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TotalCost.ToString("C2"), hideZero))</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
var useUKMPG = userConfig.UseUKMPG;
|
||||
var hideZero = userConfig.HideZero;
|
||||
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
|
||||
var useThreeDecimalsConsumption = userConfig.UseThreeDecimalGasConsumption;
|
||||
var gasCostFormat = useThreeDecimals ? "C3" : "C2";
|
||||
var gasConsumptionFormat = useThreeDecimalsConsumption ? "F3" : "F2";
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var useKwh = Model.UseKwh;
|
||||
var useHours = Model.UseHours;
|
||||
@@ -65,8 +67,9 @@
|
||||
<span class="ms-2 badge bg-primary" id="minFuelMileageLabel">@($"{translator.Translate(userLanguage, "Min Fuel Economy")}: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||
<span class="ms-2 badge bg-primary" id="maxFuelMileageLabel">@($"{translator.Translate(userLanguage, "Max Fuel Economy")}: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||
}
|
||||
<span class="ms-2 badge bg-success" id="totalDistanceLabel">@($"{translator.Translate(userLanguage, "Total Distance")}: {Model.GasRecords.Sum(x => x.DeltaMileage).ToString() ?? "0"} {distanceUnit}")</span>
|
||||
}
|
||||
<span class="ms-2 badge bg-success">@($"{translator.Translate(userLanguage, "Total Fuel Consumed")}: {Model.GasRecords.Sum(x => x.Gallons).ToString("F")}")</span>
|
||||
<span class="ms-2 badge bg-success" id="totalFuelConsumedLabel">@($"{translator.Translate(userLanguage, "Total Fuel Consumed")}: {Model.GasRecords.Sum(x => x.Gallons).ToString("F")}")</span>
|
||||
<span class="ms-2 badge bg-success" data-aggregate-type="sum">@($"{translator.Translate(userLanguage, "Total Cost")}: {Model.GasRecords.Sum(x => x.Cost).ToString(gasCostFormat)}")</span>
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
@@ -93,6 +96,13 @@
|
||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="searchGasTableRows()">@translator.Translate(userLanguage, "Search")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<div class="list-group-item">
|
||||
<input class="btn-check" type="checkbox" id="chkSelectMode">
|
||||
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
|
||||
</div>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||
<li class="dropdown-item">
|
||||
<div class="list-group-item">
|
||||
@@ -136,6 +146,12 @@
|
||||
<label class="form-check-label stretched-link" for="chkCol_UnitCost">@translator.Translate(userLanguage, "Unit Cost")</label>
|
||||
</div>
|
||||
</li>
|
||||
<li class="dropdown-item">
|
||||
<div class="list-group-item">
|
||||
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Attachment">
|
||||
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
|
||||
</div>
|
||||
</li>
|
||||
<li class="dropdown-item">
|
||||
<div class="list-group-item">
|
||||
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Notes">
|
||||
@@ -165,7 +181,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@@ -178,6 +194,7 @@
|
||||
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="fueleconomy" data-gas="fueleconomy" data-unit="@fuelEconomyUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{@translator.Translate(userLanguage, "Fuel Economy")}({fuelEconomyUnit})")</th>
|
||||
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="unitcost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Unit Cost")</th>
|
||||
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
|
||||
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
{
|
||||
@@ -191,11 +208,12 @@
|
||||
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@gasRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditGasRecordModal,@gasRecord.Id)" data-tags='@string.Join(" ", gasRecord.Tags)'>
|
||||
<td class="col-2 flex-grow-1 text-truncate" data-column="daterefueled">@gasRecord.Date</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer" data-gas-type="mileage" data-gas-aggregate="@gasRecord.DeltaMileage" data-gas-original="@gasRecord.Mileage">@(gasRecord.Mileage == default ? "---" : gasRecord.Mileage.ToString())</td>
|
||||
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="delta">@(gasRecord.DeltaMileage == default ? "---" : gasRecord.DeltaMileage)</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="consumption" data-gas-type="consumption" data-gas-aggregate="@gasRecord.Gallons">@gasRecord.Gallons.ToString("F")</td>
|
||||
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="delta" data-gas-type="totaldistance">@(gasRecord.DeltaMileage == default ? "---" : gasRecord.DeltaMileage)</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="consumption" data-gas-type="consumption" data-gas-aggregate="@gasRecord.Gallons">@gasRecord.Gallons.ToString(gasConsumptionFormat)</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="fueleconomy" data-gas-type="fueleconomy" data-aggregated='@(gasRecord.IncludeInAverage.ToString().ToLower())'>@(gasRecord.MilesPerGallon == 0 ? "---" : gasRecord.MilesPerGallon.ToString("F"))</td>
|
||||
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && gasRecord.Cost == default) ? "---" : gasRecord.Cost.ToString(gasCostFormat))</td>
|
||||
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="unitcost" data-gas-type="unitcost">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString(gasCostFormat))</td>
|
||||
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", gasRecord.Files)</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="notes">@StaticHelper.TruncateStrings(gasRecord.Notes)</td>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
{
|
||||
@@ -204,7 +222,7 @@
|
||||
var extraFieldValue = gasRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
|
||||
{
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -216,6 +234,13 @@
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -229,14 +254,19 @@
|
||||
</div>
|
||||
|
||||
<ul class="table-context-menu dropdown-menu" style="display:none;">
|
||||
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
|
||||
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
|
||||
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleGasRecords(selectedRow)">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
|
||||
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
|
||||
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
|
||||
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleGasRecords(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
|
||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
||||
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
||||
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
|
||||
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
|
||||
</ul>
|
||||
@if (userColumnPreferences.Any())
|
||||
{
|
||||
@@ -251,5 +281,12 @@
|
||||
{
|
||||
@:convertGasConsumptionUnits(decodeHTMLEntities('@consumptionUnit'), decodeHTMLEntities('@preferredGasUnit'), false);
|
||||
}
|
||||
|
||||
function getGasModelData(){
|
||||
return {
|
||||
distanceUnit: decodeHTMLEntities('@distanceUnit'),
|
||||
consumptionUnit: decodeHTMLEntities('@consumptionUnit'),
|
||||
gasCostFormat: decodeHTMLEntities('@gasCostFormat'),
|
||||
gasConsumptionFormat: decodeHTMLEntities('@gasConsumptionFormat')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -52,6 +52,12 @@
|
||||
]
|
||||
},
|
||||
options: {
|
||||
onClick: (e, a, c) => {
|
||||
showBarChartTable(a);
|
||||
},
|
||||
onHover: (e, a, c) => {
|
||||
a.length > 0 ? c.canvas.style.cursor = 'pointer' : c.canvas.style.cursor = 'default';
|
||||
},
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
var useMPG = userConfig.UseMPG;
|
||||
var useUKMPG = userConfig.UseUKMPG;
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
|
||||
var useThreeDecimalsConsumption = userConfig.UseThreeDecimalGasConsumption;
|
||||
var useKwh = Model.UseKwh;
|
||||
var useHours = Model.UseHours;
|
||||
var isNew = Model.GasRecord.Id == 0;
|
||||
var useUnitFuelCost = userConfig.UseUnitForFuelCost;
|
||||
string consumptionUnit;
|
||||
string distanceUnit;
|
||||
if (useKwh)
|
||||
@@ -62,7 +65,7 @@
|
||||
}
|
||||
</div>
|
||||
<label for="gasRecordGallons">@($"{translator.Translate(userLanguage, "Fuel Consumption")}({consumptionUnit})")</label>
|
||||
<input type="text" inputmode="decimal" id="gasRecordGallons" class="form-control" placeholder="@translator.Translate(userLanguage,"Amount of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Gallons)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimalsConsumption ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" id="gasRecordGallons" class="form-control" placeholder="@translator.Translate(userLanguage,"Amount of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Gallons)">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="gasIsFillToFull" checked="@Model.GasRecord.IsFillToFull">
|
||||
<label class="form-check-label" for="gasIsFillToFull">@translator.Translate(userLanguage,"Is Filled To Full")</label>
|
||||
@@ -75,17 +78,17 @@
|
||||
@if (isNew)
|
||||
{
|
||||
<div class="input-group">
|
||||
<input type="text" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
<input type="text" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimals ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
<div class="input-group-text">
|
||||
<select class="form-select form-select-sm" id="gasCostType">
|
||||
<option value="total">@translator.Translate(userLanguage,"Total")</option>
|
||||
<option value="unit">@translator.Translate(userLanguage,"Unit")</option>
|
||||
<!option @(useUnitFuelCost ? "" : "selected") value="total">@translator.Translate(userLanguage,"Total")</!option>
|
||||
<!option @(useUnitFuelCost ? "selected" : "") value="unit">@translator.Translate(userLanguage,"Unit")</!option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
} else
|
||||
{
|
||||
<input type="text" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimals ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
}
|
||||
<label for="gasRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="gasRecordTag">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user