Compare commits

...

68 Commits

Author SHA1 Message Date
Hargata Softworks
79633dbe1d Merge pull request #799 from hargata/Hargata/796
Update configurator
2025-01-10 12:13:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f325306e20 Update configurator 2025-01-10 12:13:34 -07:00
Hargata Softworks
4778656063 Merge pull request #798 from hargata/Hargata/796
fix mileage not included.
2025-01-10 11:02:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f7e00523c2 fix mileage not included. 2025-01-10 11:01:24 -07:00
Hargata Softworks
bf3df7230b Merge pull request #797 from hargata/Hargata/796
Hargata/796
2025-01-10 10:03:51 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c5182e0ed6 lol 2025-01-10 10:03:09 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4081438cba Defer calculations if odometer not provided. 2025-01-10 10:01:16 -07:00
Hargata Softworks
b72fe2bf37 Merge pull request #793 from hargata/Hargata/warn.dir.diff
Warn when working directory differs from content path.
2025-01-09 10:02:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4d9c687709 Warn when working directory differs from content path. 2025-01-09 10:02:14 -07:00
Hargata Softworks
792f295c45 Merge pull request #791 from hargata/Hargata/769
Fixed backup restoration for userConfig.json
2025-01-08 20:38:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
af28753558 use a static string instead. 2025-01-08 20:37:45 -07:00
DESKTOP-T0O5CDB\DESK-555BD
24fb663599 Fixed backup restoration for userConfig.json 2025-01-08 20:33:38 -07:00
Hargata Softworks
140506c9c3 Merge pull request #790 from hargata/Hargata/769
moved userConfig file into data folder.
2025-01-08 20:26:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
519f159c8c moved userConfig file into data folder. 2025-01-08 20:25:17 -07:00
Hargata Softworks
bb019cbcd9 Merge pull request #789 from hargata/Hargata/769
Fix gas record files attachment
2025-01-08 16:19:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
84219627ff Fix gas record files attachment 2025-01-08 16:18:36 -07:00
Hargata Softworks
f218e878c6 Merge pull request #788 from hargata/Hargata/769
Document Upload Endpoint
2025-01-08 15:55:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
520f47955b cleaned up usings. 2025-01-08 14:15:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8110ee18f1 Create endpoint to upload documents and add ability to attach uploaded documents to records. 2025-01-08 14:14:23 -07:00
Hargata Softworks
55bf817310 Merge pull request #782 from hargata/Hargata/697
added API method that generates a calendar of reminders for the user.
2025-01-08 12:39:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f2f55f8118 Restore favicon, previously was served up automatically by browser but now we want to link it explicitly. also brought back the security features for accessing static files. 2025-01-08 12:38:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f48e7cd0d4 update Controller Action Name, 2025-01-08 11:11:29 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c82e0c8b9b Use a MD5 hash to get exactly 16 bytes so the GUID is always valid and identical for the calendar event with same date and description. 2025-01-08 10:49:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c72877e16b Merge branch 'main' into Hargata/697 2025-01-08 10:40:10 -07:00
Hargata Softworks
ccc9076397 Merge pull request #786 from hargata/Hargata/785
move files out of webrootpath.
2025-01-08 10:13:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1c716ecf4a bump version 2025-01-08 10:12:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
2cc471b944 move files out of webrootpath. 2025-01-08 10:10:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a6c450109a added API method that generates a calendar of reminders for the user. 2025-01-07 08:43:25 -07:00
Hargata Softworks
e13707305b Merge pull request #776 from hargata/Hargata/745
Fix annoying bug on macOS devices where context menu hides as soon as…
2024-12-31 09:21:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
68bc0383f4 Fix annoying bug on macOS devices where context menu hides as soon as it shows up. 2024-12-31 09:21:05 -07:00
Hargata Softworks
878f4f3687 Merge pull request #771 from hargata/Hargata/745
added bottom padding for garage view.
2024-12-16 17:32:20 -07:00
DESKTOP-T0O5CDB\DESK-555BD
48a721adda added bottom padding for garage view. 2024-12-16 17:31:30 -07:00
Hargata Softworks
548e8da78c Merge pull request #747 from hargata/Hargata/745
1.4.2 Changes
2024-12-16 09:45:01 -07:00
Hargata Softworks
beb28214cf Merge pull request #767 from hargata/Hargata/758
allow users to create odometer records from existing records.
2024-12-13 11:37:47 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b4bb31491b allow users to create odometer records from existing records. 2024-12-13 11:31:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0b5f007327 Styling fixes. 2024-12-13 10:59:53 -07:00
Hargata Softworks
d31a4aeea3 Merge pull request #766 from hargata/Hargata/761
Add Date Range Filter to Vehicle Maintenance Report
2024-12-12 11:58:44 -07:00
DESKTOP-T0O5CDB\DESK-555BD
97acd873eb Added date range filter. 2024-12-12 11:56:05 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5cf3538be3 garage view enhancement for mobile devices. 2024-12-11 19:19:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a34921701d webhook payload update. 2024-12-11 14:40:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5fcf54de09 cleaned up some code. 2024-12-11 12:32:40 -07:00
Hargata Softworks
68372bf995 Merge pull request #764 from hargata/Hargata/763
Hargata/763
2024-12-11 12:29:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9f99ac1531 Add tags to be exported. 2024-12-11 12:28:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b7384fda6b Spacing and translation keys. 2024-12-07 22:34:02 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8f4d610825 add spacing in report parameter. 2024-12-07 22:25:28 -07:00
Hargata Softworks
6bbf4b2575 Merge pull request #760 from hargata/Hargata/572
Hargata/572
2024-12-07 12:03:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
32db884620 Allow users to filter records by tags in the consolidated report. 2024-12-07 11:50:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1b736b36f8 Added delete records. 2024-12-06 11:34:04 -07:00
Hargata Softworks
22dfe5eb04 Merge pull request #756 from hargata/Hargata/574
Created nicer machine payloads for webhooks.
2024-12-06 08:43:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8fd49e20d5 added support for discord. 2024-12-06 08:42:25 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c791070126 Fixed webhook stuff. 2024-12-06 08:11:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
807603fd4f Created nicer machine payloads for webhooks. 2024-12-05 19:08:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a944e5e5ab See #744 2024-12-05 08:10:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
33e916cac2 force a page refresh if there are oudated recurring tax records on vehicle load. 2024-12-04 21:11:58 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a99d71dc41 pattern 2024-12-04 11:54:43 -07:00
DESKTOP-T0O5CDB\DESK-555BD
67d91f1a0d See 751 2024-12-04 11:38:17 -07:00
DESKTOP-T0O5CDB\DESK-555BD
de2db7d24e added security checks 2024-12-04 09:05:49 -07:00
Hargata Softworks
b360f77ea0 Merge pull request #754 from hargata/Hargata/752.2
Fixed naming policy.
2024-12-04 08:54:43 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e869528522 Fixed naming policy. 2024-12-04 08:15:46 -07:00
Hargata Softworks
7c0317d232 Merge pull request #753 from hargata/Hargata/752
Allow for JSON Body in POST/PUT API Methods
2024-12-03 14:15:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0e49497da1 added invariant exports to get methods. 2024-12-03 13:40:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0380382c47 allow API methods to receive JSON with rich datatypes. 2024-12-03 11:35:11 -07:00
Hargata Softworks
23201b8c86 Merge pull request #748 from hargata/Hargata/541
Added PUT API Methods
2024-11-30 12:25:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5f357c9f84 Fix CollaboratorFilter bug. 2024-11-30 12:23:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
244272621c bump version 2024-11-30 10:19:51 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5792b84880 Added PUT API Methods 2024-11-30 10:17:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8e644093f9 added try catch 2024-11-29 13:47:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9421cb57ca Create past records for outdated tax records. 2024-11-29 13:37:37 -07:00
52 changed files with 2396 additions and 329 deletions

11
.gitignore vendored
View File

@@ -1,15 +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
data/widgets.html

View File

@@ -34,6 +34,7 @@ namespace CarCareTracker.Controllers
private readonly IFileHelper _fileHelper;
private readonly IMailHelper _mailHelper;
private readonly IConfigHelper _config;
private readonly IWebHostEnvironment _webEnv;
public APIController(IVehicleDataAccess dataAccess,
IGasHelper gasHelper,
IReminderHelper reminderHelper,
@@ -55,7 +56,8 @@ namespace CarCareTracker.Controllers
IConfigHelper config,
IUserLogic userLogic,
IVehicleLogic vehicleLogic,
IOdometerLogic odometerLogic)
IOdometerLogic odometerLogic,
IWebHostEnvironment webEnv)
{
_dataAccess = dataAccess;
_noteDataAccess = noteDataAccess;
@@ -79,6 +81,7 @@ namespace CarCareTracker.Controllers
_vehicleLogic = vehicleLogic;
_fileHelper = fileHelper;
_config = config;
_webEnv = webEnv;
}
public IActionResult Index()
{
@@ -126,7 +129,14 @@ namespace CarCareTracker.Controllers
}
var apiResult = _vehicleLogic.GetVehicleInfo(vehicles);
return Json(apiResult);
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(apiResult, StaticHelper.GetInvariantOption());
}
else
{
return Json(apiResult);
}
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
@@ -143,6 +153,7 @@ namespace CarCareTracker.Controllers
return Json(convertedOdometer);
}
}
#region ServiceRecord
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
[Route("/api/vehicle/servicerecords")]
@@ -155,12 +166,23 @@ namespace CarCareTracker.Controllers
return Json(response);
}
var vehicleRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields });
return Json(result);
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields, Files = x.Files, Tags = string.Join(' ', x.Tags) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
} else
{
return Json(result);
}
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/servicerecords/add")]
[Consumes("application/json")]
public IActionResult AddServiceRecordJson(int vehicleId, [FromBody] GenericRecordExportModel input) => AddServiceRecord(vehicleId, input);
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/servicerecords/add")]
public IActionResult AddServiceRecord(int vehicleId, GenericRecordExportModel input)
{
if (vehicleId == default)
@@ -187,6 +209,7 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord);
@@ -201,7 +224,7 @@ namespace CarCareTracker.Controllers
};
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Service Record via API - Description: {serviceRecord.Description}");
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(serviceRecord, "servicerecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Service Record Added"));
}
catch (Exception ex)
@@ -210,6 +233,88 @@ namespace CarCareTracker.Controllers
return Json(OperationResponse.Failed(ex.Message));
}
}
[HttpDelete]
[Route("/api/vehicle/servicerecords/delete")]
public IActionResult DeleteServiceRecord(int id)
{
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(id);
if (existingRecord == null || existingRecord.Id == default)
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
//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.api", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, "Service Record Deleted"));
}
[HttpPut]
[Route("/api/vehicle/servicerecords/update")]
[Consumes("application/json")]
public IActionResult UpdateServiceRecordJson([FromBody] GenericRecordExportModel input) => UpdateServiceRecord(input);
[HttpPut]
[Route("/api/vehicle/servicerecords/update")]
public IActionResult UpdateServiceRecord(GenericRecordExportModel input)
{
if (string.IsNullOrWhiteSpace(input.Id) ||
string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
string.IsNullOrWhiteSpace(input.Cost))
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Description, Odometer, and Cost cannot be empty."));
}
try
{
//retrieve existing record
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(int.Parse(input.Id));
if (existingRecord != null && existingRecord.Id == int.Parse(input.Id))
{
//check if user has access to the vehicleId
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
existingRecord.Date = DateTime.Parse(input.Date);
existingRecord.Mileage = int.Parse(input.Odometer);
existingRecord.Description = input.Description;
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.Cost = decimal.Parse(input.Cost);
existingRecord.Files = input.Files;
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_serviceRecordDataAccess.SaveServiceRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "servicerecord.update.api", User.Identity.Name));
} else
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
return Json(OperationResponse.Succeed("Service Record Updated"));
}
catch (Exception ex)
{
Response.StatusCode = 500;
return Json(OperationResponse.Failed(ex.Message));
}
}
#endregion
#region RepairRecord
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
[Route("/api/vehicle/repairrecords")]
@@ -222,12 +327,24 @@ namespace CarCareTracker.Controllers
return Json(response);
}
var vehicleRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields });
return Json(result);
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields, Files = x.Files, Tags = string.Join(' ', x.Tags) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
}
else
{
return Json(result);
}
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/repairrecords/add")]
[Consumes("application/json")]
public IActionResult AddRepairRecordJson(int vehicleId, [FromBody] GenericRecordExportModel input) => AddRepairRecord(vehicleId, input);
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/repairrecords/add")]
public IActionResult AddRepairRecord(int vehicleId, GenericRecordExportModel input)
{
if (vehicleId == default)
@@ -254,6 +371,7 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(repairRecord);
@@ -268,7 +386,8 @@ namespace CarCareTracker.Controllers
};
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Repair Record via API - Description: {repairRecord.Description}");
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(repairRecord, "repairrecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Repair Record Added"));
}
catch (Exception ex)
@@ -277,6 +396,89 @@ namespace CarCareTracker.Controllers
return Json(OperationResponse.Failed(ex.Message));
}
}
[HttpDelete]
[Route("/api/vehicle/repairrecords/delete")]
public IActionResult DeleteRepairRecord(int id)
{
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(id);
if (existingRecord == null || existingRecord.Id == default)
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
//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.api", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, "Repair Record Deleted"));
}
[HttpPut]
[Route("/api/vehicle/repairrecords/update")]
[Consumes("application/json")]
public IActionResult UpdateRepairRecordJson([FromBody] GenericRecordExportModel input) => UpdateRepairRecord(input);
[HttpPut]
[Route("/api/vehicle/repairrecords/update")]
public IActionResult UpdateRepairRecord(GenericRecordExportModel input)
{
if (string.IsNullOrWhiteSpace(input.Id) ||
string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
string.IsNullOrWhiteSpace(input.Cost))
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Description, Odometer, and Cost cannot be empty."));
}
try
{
//retrieve existing record
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(int.Parse(input.Id));
if (existingRecord != null && existingRecord.Id == int.Parse(input.Id))
{
//check if user has access to the vehicleId
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
existingRecord.Date = DateTime.Parse(input.Date);
existingRecord.Mileage = int.Parse(input.Odometer);
existingRecord.Description = input.Description;
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.Cost = decimal.Parse(input.Cost);
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Files = input.Files;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "repairrecord.update.api", User.Identity.Name));
}
else
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
return Json(OperationResponse.Succeed("Repair Record Updated"));
}
catch (Exception ex)
{
Response.StatusCode = 500;
return Json(OperationResponse.Failed(ex.Message));
}
}
#endregion
#region UpgradeRecord
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
[Route("/api/vehicle/upgraderecords")]
@@ -289,12 +491,24 @@ namespace CarCareTracker.Controllers
return Json(response);
}
var vehicleRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields });
return Json(result);
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields, Files = x.Files, Tags = string.Join(' ', x.Tags) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
}
else
{
return Json(result);
}
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/upgraderecords/add")]
[Consumes("application/json")]
public IActionResult AddUpgradeRecordJson(int vehicleId, [FromBody] GenericRecordExportModel input) => AddUpgradeRecord(vehicleId, input);
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/upgraderecords/add")]
public IActionResult AddUpgradeRecord(int vehicleId, GenericRecordExportModel input)
{
if (vehicleId == default)
@@ -321,6 +535,7 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord);
@@ -335,7 +550,7 @@ namespace CarCareTracker.Controllers
};
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Upgrade Record via API - Description: {upgradeRecord.Description}");
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(upgradeRecord, "upgraderecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Upgrade Record Added"));
}
catch (Exception ex)
@@ -344,6 +559,89 @@ namespace CarCareTracker.Controllers
return Json(OperationResponse.Failed(ex.Message));
}
}
[HttpDelete]
[Route("/api/vehicle/upgraderecords/delete")]
public IActionResult DeleteUpgradeRecord(int id)
{
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(id);
if (existingRecord == null || existingRecord.Id == default)
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
//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.api", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result,"Upgrade Record Deleted"));
}
[HttpPut]
[Route("/api/vehicle/upgraderecords/update")]
[Consumes("application/json")]
public IActionResult UpdateUpgradeRecordJson([FromBody] GenericRecordExportModel input) => UpdateUpgradeRecord(input);
[HttpPut]
[Route("/api/vehicle/upgraderecords/update")]
public IActionResult UpdateUpgradeRecord(GenericRecordExportModel input)
{
if (string.IsNullOrWhiteSpace(input.Id) ||
string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
string.IsNullOrWhiteSpace(input.Cost))
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Description, Odometer, and Cost cannot be empty."));
}
try
{
//retrieve existing record
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(int.Parse(input.Id));
if (existingRecord != null && existingRecord.Id == int.Parse(input.Id))
{
//check if user has access to the vehicleId
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
existingRecord.Date = DateTime.Parse(input.Date);
existingRecord.Mileage = int.Parse(input.Odometer);
existingRecord.Description = input.Description;
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.Cost = decimal.Parse(input.Cost);
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Files = input.Files;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "upgraderecord.update.api", User.Identity.Name));
}
else
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
return Json(OperationResponse.Succeed("Upgrade Record Updated"));
}
catch (Exception ex)
{
Response.StatusCode = 500;
return Json(OperationResponse.Failed(ex.Message));
}
}
#endregion
#region TaxRecord
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
[Route("/api/vehicle/taxrecords")]
@@ -355,9 +653,56 @@ namespace CarCareTracker.Controllers
Response.StatusCode = 400;
return Json(response);
}
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Select(x => new TaxRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields });
return Json(result);
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Select(x => new TaxRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields, Files = x.Files, Tags = string.Join(' ', x.Tags) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
}
else
{
return Json(result);
}
}
[HttpGet]
[Route("/api/vehicle/taxrecords/check")]
public IActionResult CheckRecurringTaxRecords()
{
List<Vehicle> vehicles = new List<Vehicle>();
try
{
var result = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
result = _userLogic.FilterUserVehicles(result, GetUserID());
}
vehicles.AddRange(result);
int vehiclesUpdated = 0;
foreach(Vehicle vehicle in vehicles)
{
var updateResult = _vehicleLogic.UpdateRecurringTaxes(vehicle.Id);
if (updateResult)
{
vehiclesUpdated++;
}
}
if (vehiclesUpdated != default)
{
return Json(OperationResponse.Succeed($"Recurring Taxes for {vehiclesUpdated} Vehicles Updated!"));
} else
{
return Json(OperationResponse.Succeed("No Recurring Taxes Updated"));
}
}
catch (Exception ex)
{
return Json(OperationResponse.Failed($"No Recurring Taxes Updated Due To Error: {ex.Message}"));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/taxrecords/add")]
[Consumes("application/json")]
public IActionResult AddTaxRecordJson(int vehicleId, [FromBody] TaxRecordExportModel input) => AddTaxRecord(vehicleId, input);
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/taxrecords/add")]
@@ -385,10 +730,12 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Tax Record via API - Description: {taxRecord.Description}");
_vehicleLogic.UpdateRecurringTaxes(vehicleId);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(taxRecord, "taxrecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Tax Record Added"));
}
catch (Exception ex)
@@ -397,6 +744,82 @@ namespace CarCareTracker.Controllers
return Json(OperationResponse.Failed(ex.Message));
}
}
[HttpDelete]
[Route("/api/vehicle/taxrecords/delete")]
public IActionResult DeleteTaxRecord(int id)
{
var existingRecord = _taxRecordDataAccess.GetTaxRecordById(id);
if (existingRecord == null || existingRecord.Id == default)
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
var result = _taxRecordDataAccess.DeleteTaxRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(existingRecord, "taxrecord.delete.api", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, "Tax Record Deleted"));
}
[HttpPut]
[Route("/api/vehicle/taxrecords/update")]
[Consumes("application/json")]
public IActionResult UpdateTaxRecordJson([FromBody] TaxRecordExportModel input) => UpdateTaxRecord(input);
[HttpPut]
[Route("/api/vehicle/taxrecords/update")]
public IActionResult UpdateTaxRecord(TaxRecordExportModel input)
{
if (string.IsNullOrWhiteSpace(input.Id) ||
string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Cost))
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Description, and Cost cannot be empty."));
}
try
{
//retrieve existing record
var existingRecord = _taxRecordDataAccess.GetTaxRecordById(int.Parse(input.Id));
if (existingRecord != null && existingRecord.Id == int.Parse(input.Id))
{
//check if user has access to the vehicleId
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
existingRecord.Date = DateTime.Parse(input.Date);
existingRecord.Description = input.Description;
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.Cost = decimal.Parse(input.Cost);
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Files = input.Files;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_taxRecordDataAccess.SaveTaxRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(existingRecord, "taxrecord.update.api", User.Identity.Name));
}
else
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
return Json(OperationResponse.Succeed("Tax Record Updated"));
}
catch (Exception ex)
{
Response.StatusCode = 500;
return Json(OperationResponse.Failed(ex.Message));
}
}
#endregion
#region OdometerRecord
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
[Route("/api/vehicle/odometerrecords/latest")]
@@ -428,12 +851,24 @@ namespace CarCareTracker.Controllers
{
vehicleRecords = _odometerLogic.AutoConvertOdometerRecord(vehicleRecords);
}
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), InitialOdometer = x.InitialMileage.ToString(), Odometer = x.Mileage.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields });
return Json(result);
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), InitialOdometer = x.InitialMileage.ToString(), Odometer = x.Mileage.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields, Files = x.Files, Tags = string.Join(' ', x.Tags) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
}
else
{
return Json(result);
}
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/odometerrecords/add")]
[Consumes("application/json")]
public IActionResult AddOdometerRecordJson(int vehicleId, [FromBody] OdometerRecordExportModel input) => AddOdometerRecord(vehicleId, input);
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/odometerrecords/add")]
public IActionResult AddOdometerRecord(int vehicleId, OdometerRecordExportModel input)
{
if (vehicleId == default)
@@ -445,7 +880,7 @@ namespace CarCareTracker.Controllers
string.IsNullOrWhiteSpace(input.Odometer))
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Input object invalid, Date and Odometer cannot be empty."));
return Json(OperationResponse.Failed("Input object invalid, Date, and Odometer cannot be empty."));
}
try
{
@@ -457,10 +892,11 @@ namespace CarCareTracker.Controllers
InitialMileage = (string.IsNullOrWhiteSpace(input.InitialOdometer) || int.Parse(input.InitialOdometer) == default) ? _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()) : int.Parse(input.InitialOdometer),
Mileage = int.Parse(input.Odometer),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Odometer Record via API - Mileage: {odometerRecord.Mileage.ToString()}");
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(odometerRecord, "odometerrecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Odometer Record Added"));
} catch (Exception ex)
{
@@ -468,6 +904,82 @@ namespace CarCareTracker.Controllers
return Json(OperationResponse.Failed(ex.Message));
}
}
[HttpDelete]
[Route("/api/vehicle/odometerrecords/delete")]
public IActionResult DeleteOdometerRecord(int id)
{
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(id);
if (existingRecord == null || existingRecord.Id == default)
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(existingRecord, "odometerrecord.delete.api", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, "Odometer Record Deleted"));
}
[HttpPut]
[Route("/api/vehicle/odometerrecords/update")]
[Consumes("application/json")]
public IActionResult UpdateOdometerRecordJson([FromBody] OdometerRecordExportModel input) => UpdateOdometerRecord(input);
[HttpPut]
[Route("/api/vehicle/odometerrecords/update")]
public IActionResult UpdateOdometerRecord(OdometerRecordExportModel input)
{
if (string.IsNullOrWhiteSpace(input.Id) ||
string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.InitialOdometer) ||
string.IsNullOrWhiteSpace(input.Odometer))
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Initial Odometer, and Odometer cannot be empty."));
}
try
{
//retrieve existing record
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(int.Parse(input.Id));
if (existingRecord != null && existingRecord.Id == int.Parse(input.Id))
{
//check if user has access to the vehicleId
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
existingRecord.Date = DateTime.Parse(input.Date);
existingRecord.Mileage = int.Parse(input.Odometer);
existingRecord.InitialMileage = int.Parse(input.InitialOdometer);
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Files = input.Files;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(existingRecord, "odometerrecord.update.api", User.Identity.Name));
}
else
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
return Json(OperationResponse.Succeed("Odometer Record Updated"));
}
catch (Exception ex)
{
Response.StatusCode = 500;
return Json(OperationResponse.Failed(ex.Message));
}
}
#endregion
#region GasRecord
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
[Route("/api/vehicle/gasrecords")]
@@ -482,6 +994,7 @@ namespace CarCareTracker.Controllers
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
var result = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG)
.Select(x => new GasRecordExportModel {
Id = x.Id.ToString(),
Date = x.Date,
Odometer = x.Mileage.ToString(),
Cost = x.Cost.ToString(),
@@ -490,13 +1003,27 @@ namespace CarCareTracker.Controllers
IsFillToFull = x.IsFillToFull.ToString(),
MissedFuelUp = x.MissedFuelUp.ToString(),
Notes = x.Notes,
ExtraFields = x.ExtraFields
ExtraFields = x.ExtraFields,
Files = x.Files,
Tags = string.Join(' ', x.Tags)
});
return Json(result);
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
}
else
{
return Json(result);
}
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/gasrecords/add")]
[Consumes("application/json")]
public IActionResult AddGasRecordJson(int vehicleId, [FromBody] GasRecordExportModel input) => AddGasRecord(vehicleId, input);
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/gasrecords/add")]
public IActionResult AddGasRecord(int vehicleId, GasRecordExportModel input)
{
if (vehicleId == default)
@@ -528,6 +1055,7 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord);
@@ -542,7 +1070,7 @@ namespace CarCareTracker.Controllers
};
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Gas record via API - Mileage: {gasRecord.Mileage.ToString()}");
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(gasRecord, "gasrecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Gas Record Added"));
}
catch (Exception ex)
@@ -551,6 +1079,87 @@ namespace CarCareTracker.Controllers
return Json(OperationResponse.Failed(ex.Message));
}
}
[HttpDelete]
[Route("/api/vehicle/gasrecords/delete")]
public IActionResult DeleteGasRecord(int id)
{
var existingRecord = _gasRecordDataAccess.GetGasRecordById(id);
if (existingRecord == null || existingRecord.Id == default)
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
var result = _gasRecordDataAccess.DeleteGasRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(existingRecord, "gasrecord.delete.api", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, "Gas Record Deleted"));
}
[HttpPut]
[Route("/api/vehicle/gasrecords/update")]
[Consumes("application/json")]
public IActionResult UpdateGasRecordJson([FromBody] GasRecordExportModel input) => UpdateGasRecord(input);
[HttpPut]
[Route("/api/vehicle/gasrecords/update")]
public IActionResult UpdateGasRecord(GasRecordExportModel input)
{
if (string.IsNullOrWhiteSpace(input.Id) ||
string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
string.IsNullOrWhiteSpace(input.FuelConsumed) ||
string.IsNullOrWhiteSpace(input.Cost) ||
string.IsNullOrWhiteSpace(input.IsFillToFull) ||
string.IsNullOrWhiteSpace(input.MissedFuelUp))
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Input object invalid, Id, Date, Odometer, FuelConsumed, IsFillToFull, MissedFuelUp, and Cost cannot be empty."));
}
try
{
//retrieve existing record
var existingRecord = _gasRecordDataAccess.GetGasRecordById(int.Parse(input.Id));
if (existingRecord != null && existingRecord.Id == int.Parse(input.Id))
{
//check if user has access to the vehicleId
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
existingRecord.Date = DateTime.Parse(input.Date);
existingRecord.Mileage = int.Parse(input.Odometer);
existingRecord.Gallons = decimal.Parse(input.FuelConsumed);
existingRecord.IsFillToFull = bool.Parse(input.IsFillToFull);
existingRecord.MissedFuelUp = bool.Parse(input.MissedFuelUp);
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.Cost = decimal.Parse(input.Cost);
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Files = input.Files;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_gasRecordDataAccess.SaveGasRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(existingRecord, "gasrecord.update.api", User.Identity.Name));
}
else
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
return Json(OperationResponse.Succeed("Gas Record Updated"));
}
catch (Exception ex)
{
Response.StatusCode = 500;
return Json(OperationResponse.Failed(ex.Message));
}
}
#endregion
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
[Route("/api/vehicle/reminders")]
@@ -564,7 +1173,59 @@ namespace CarCareTracker.Controllers
var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).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()});
return Json(results);
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(results, StaticHelper.GetInvariantOption());
}
else
{
return Json(results);
}
}
[HttpGet]
[Route("/api/calendar")]
public IActionResult Calendar()
{
var vehiclesStored = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
var reminders = _vehicleLogic.GetReminders(vehiclesStored, true);
var calendarContent = StaticHelper.RemindersToCalendar(reminders);
return File(calendarContent, "text/calendar");
}
[HttpPost]
[Route("/api/documents/upload")]
public IActionResult UploadDocument(List<IFormFile> documents)
{
if (documents.Any())
{
List<UploadedFiles> uploadedFiles = new List<UploadedFiles>();
string uploadDirectory = "documents/";
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
foreach (IFormFile document in documents)
{
string fileName = Guid.NewGuid() + Path.GetExtension(document.FileName);
string filePath = Path.Combine(uploadPath, fileName);
using (var stream = System.IO.File.Create(filePath))
{
document.CopyTo(stream);
}
uploadedFiles.Add(new UploadedFiles
{
Location = Path.Combine("/", uploadDirectory, fileName),
Name = Path.GetFileName(document.FileName)
});
}
return Json(uploadedFiles);
} else
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("No files to upload"));
}
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]

View File

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

View File

@@ -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,
@@ -100,16 +111,16 @@ namespace CarCareTracker.Controllers
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 = DeleteGasRecordWithChecks(gasRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Gas Record - Id: {gasRecordId}");
}
return Json(result);
}
[HttpPost]

View File

@@ -25,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";

View File

@@ -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,6 +49,11 @@ 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)
@@ -54,16 +65,16 @@ namespace CarCareTracker.Controllers
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 = DeleteNoteWithChecks(noteId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Note - Id: {noteId}");
}
return Json(result);
}
[HttpPost]

View File

@@ -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
{
@@ -149,16 +160,16 @@ namespace CarCareTracker.Controllers
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 = DeleteOdometerRecordWithChecks(odometerRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Odometer Record - Id: {odometerRecordId}");
}
return Json(result);
}
}

View File

@@ -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)
{
@@ -35,18 +40,23 @@ namespace CarCareTracker.Controllers
}
if (planRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(planRecord.DeletedRequisitionHistory, planRecord.Description);
_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)
@@ -67,6 +77,16 @@ 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);
}
@@ -78,6 +98,11 @@ namespace CarCareTracker.Controllers
{
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);
@@ -96,6 +121,11 @@ namespace CarCareTracker.Controllers
{
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
@@ -156,6 +186,11 @@ namespace CarCareTracker.Controllers
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);
@@ -239,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
{
@@ -271,12 +311,12 @@ namespace CarCareTracker.Controllers
//restore any requisitioned supplies if it has not been converted to other record types.
if (existingRecord.RequisitionHistory.Any() && existingRecord.Progress != PlanProgress.Done)
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
_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);
}

View File

@@ -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
{
@@ -158,16 +169,16 @@ namespace CarCareTracker.Controllers
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 = DeleteReminderRecordWithChecks(reminderRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Reminder - Id: {reminderRecordId}");
}
return Json(result);
}
}

View File

@@ -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
@@ -48,7 +53,7 @@ namespace CarCareTracker.Controllers
}
if (collisionRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(collisionRecord.DeletedRequisitionHistory, collisionRecord.Description);
_vehicleLogic.RestoreSupplyRecordsByUsage(collisionRecord.DeletedRequisitionHistory, collisionRecord.Description);
}
//push back any reminders
if (collisionRecord.ReminderRecordId.Any())
@@ -61,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);
}
@@ -74,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
{
@@ -102,19 +112,19 @@ namespace CarCareTracker.Controllers
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
_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 = DeleteCollisionRecordWithChecks(collisionRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Repair Record - Id: {collisionRecordId}");
}
return Json(result);
}
}

View File

@@ -349,9 +349,57 @@ namespace CarCareTracker.Controllers
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))
{
@@ -388,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");
@@ -406,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);
@@ -425,7 +465,7 @@ 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,
@@ -436,7 +476,7 @@ namespace CarCareTracker.Controllers
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,
@@ -446,7 +486,7 @@ namespace CarCareTracker.Controllers
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,
@@ -456,7 +496,7 @@ namespace CarCareTracker.Controllers
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,

View File

@@ -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
@@ -48,7 +53,7 @@ namespace CarCareTracker.Controllers
}
if (serviceRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(serviceRecord.DeletedRequisitionHistory, serviceRecord.Description);
_vehicleLogic.RestoreSupplyRecordsByUsage(serviceRecord.DeletedRequisitionHistory, serviceRecord.Description);
}
//push back any reminders
if (serviceRecord.ReminderRecordId.Any())
@@ -61,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);
}
@@ -74,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
{
@@ -102,19 +112,19 @@ namespace CarCareTracker.Controllers
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
_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 = DeleteServiceRecordWithChecks(serviceRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Service Record - Id: {serviceRecordId}");
}
return Json(result);
}
}

View File

@@ -73,44 +73,7 @@ namespace CarCareTracker.Controllers
}
return results;
}
private 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}");
}
}
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetSupplyRecordsByVehicleId(int vehicleId)
@@ -187,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);
}
@@ -236,16 +199,16 @@ namespace CarCareTracker.Controllers
}
}
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 = DeleteSupplyRecordWithChecks(supplyRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Supply Record - Id: {supplyRecordId}");
}
return Json(result);
}
}

View File

@@ -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
{
@@ -119,16 +105,16 @@ namespace CarCareTracker.Controllers
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 = DeleteTaxRecordWithChecks(taxRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Tax Record - Id: {taxRecordId}");
}
return Json(result);
}
}

View File

@@ -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
@@ -48,7 +53,7 @@ namespace CarCareTracker.Controllers
}
if (upgradeRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(upgradeRecord.DeletedRequisitionHistory, upgradeRecord.Description);
_vehicleLogic.RestoreSupplyRecordsByUsage(upgradeRecord.DeletedRequisitionHistory, upgradeRecord.Description);
}
//push back any reminders
if (upgradeRecord.ReminderRecordId.Any())
@@ -61,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);
}
@@ -74,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
{
@@ -102,19 +112,19 @@ namespace CarCareTracker.Controllers
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
_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 = DeleteUpgradeRecordWithChecks(upgradeRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Upgrade Record - Id: {upgradeRecordId}");
}
return Json(result);
}
}

View File

@@ -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,10 @@ 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}");
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 +163,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);
}
@@ -389,7 +388,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);
}
@@ -431,7 +430,7 @@ namespace CarCareTracker.Controllers
}
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,7 +489,7 @@ 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);
}
@@ -582,7 +581,7 @@ namespace CarCareTracker.Controllers
}
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);
}
@@ -717,7 +716,71 @@ namespace CarCareTracker.Controllers
}
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)} - to Vehicle Ids: {string.Join(",", vehicleIds)}");
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);
}

8
Enum/TagFilter.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public enum TagFilter
{
Exclude = 0,
IncludeOnly = 1
}
}

View File

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

View File

@@ -24,7 +24,8 @@ namespace CarCareTracker.Helper
bool GetServerEnableShopSupplies();
string GetServerPostgresConnection();
string GetAllowedFileUploadExtensions();
public bool DeleteUserConfig(int userId);
bool DeleteUserConfig(int userId);
bool GetInvariantApi();
}
public class ConfigHelper : IConfigHelper
{
@@ -51,6 +52,10 @@ namespace CarCareTracker.Helper
{
return CheckBool(CheckString("LUBELOGGER_CUSTOM_WIDGETS"));
}
public bool GetInvariantApi()
{
return CheckBool(CheckString("LUBELOGGER_INVARIANT_API"));
}
public string GetMOTD()
{
var motd = CheckString("LUBELOGGER_MOTD");
@@ -224,6 +229,7 @@ namespace CarCareTracker.Helper
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))),

View File

@@ -34,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))
{
@@ -72,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;
@@ -94,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
@@ -105,10 +105,10 @@ namespace CarCareTracker.Helper
var translationPath = Path.Combine(tempPath, "translations");
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath);
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
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);
@@ -130,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);
@@ -152,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);
@@ -186,9 +186,9 @@ namespace CarCareTracker.Helper
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);
}
@@ -203,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;
@@ -227,10 +227,10 @@ 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;
@@ -301,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);
@@ -319,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);

View File

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

View File

@@ -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,9 +12,10 @@ namespace CarCareTracker.Helper
/// </summary>
public static class StaticHelper
{
public const string VersionNumber = "1.4.1";
public const string VersionNumber = "1.4.3";
public const string DbName = "data/cartracker.db";
public const string UserConfigPath = "config/userConfig.json";
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";
@@ -239,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())
@@ -260,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))
{
@@ -308,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");
}
@@ -316,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)
{
@@ -367,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";
}
@@ -399,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)
{
@@ -421,8 +520,9 @@ namespace CarCareTracker.Helper
if (string.IsNullOrWhiteSpace(name))
{
return string.Empty;
} else
{
}
else
{
try
{
string cleanedName = name.Contains("_") ? name.Replace("_", "-") : name;
@@ -435,7 +535,8 @@ namespace CarCareTracker.Helper
{
return displayName;
}
} catch (Exception ex)
}
catch (Exception ex)
{
return name;
}
@@ -606,7 +707,8 @@ namespace CarCareTracker.Helper
if (input == 0M.ToString("C2") && hideZero)
{
return "---";
} else
}
else
{
return string.IsNullOrWhiteSpace(decorations) ? input : $"{input}{decorations}";
}
@@ -622,6 +724,13 @@ namespace CarCareTracker.Helper
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();
@@ -659,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);
}
}
}

View File

@@ -1,4 +1,5 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Controllers;
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
@@ -17,6 +18,8 @@ namespace CarCareTracker.Logic
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
{
@@ -29,6 +32,10 @@ namespace CarCareTracker.Logic
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,
@@ -38,7 +45,10 @@ namespace CarCareTracker.Logic
IOdometerRecordDataAccess odometerRecordDataAccess,
IReminderRecordDataAccess reminderRecordDataAccess,
IPlanRecordDataAccess planRecordDataAccess,
IReminderHelper reminderHelper
IReminderHelper reminderHelper,
IVehicleDataAccess dataAccess,
ISupplyRecordDataAccess supplyRecordDataAccess,
ILogger<VehicleLogic> logger
) {
_serviceRecordDataAccess = serviceRecordDataAccess;
_gasRecordDataAccess = gasRecordDataAccess;
@@ -49,6 +59,9 @@ namespace CarCareTracker.Logic
_planRecordDataAccess = planRecordDataAccess;
_reminderRecordDataAccess = reminderRecordDataAccess;
_reminderHelper = reminderHelper;
_dataAccess = dataAccess;
_supplyRecordDataAccess = supplyRecordDataAccess;
_logger = logger;
}
public VehicleRecords GetVehicleRecords(int vehicleId)
{
@@ -317,5 +330,96 @@ namespace CarCareTracker.Logic
}
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;
return 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;
//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 = originalDate.AddMonths(monthInterval * monthMultiplier);
monthMultiplier++;
var nextnextDate = 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}");
}
}
}
}
}

138
Models/API/TypeConverter.cs Normal file
View 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);
}
}
}
}

View File

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

View File

@@ -2,7 +2,12 @@
{
public class ReportParameter
{
public List<string> VisibleColumns { get; set; } = new List<string>();
public List<string> ExtraFields { get; set; } = new List<string>();
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; } = "";
}
}

View File

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

View File

@@ -1,4 +1,6 @@
namespace CarCareTracker.Models
using System.Text.Json.Serialization;
namespace CarCareTracker.Models
{
/// <summary>
/// Import model used for importing records via CSV.
@@ -41,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
{
@@ -90,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
@@ -103,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>();
}
}

View 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}"
};
}
}
}

View File

@@ -23,6 +23,7 @@
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; }

View File

@@ -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();
@@ -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,11 +106,55 @@ 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");
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.Add("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.Add("Cache-Control", "no-store");
if (!ctx.Context.User.Identity.IsAuthenticated)

View File

@@ -28,7 +28,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/vehicles</code>
@@ -42,7 +42,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/info</code>
@@ -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>
@@ -118,12 +118,50 @@
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>
@@ -137,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>
@@ -156,12 +194,51 @@
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>
@@ -175,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>
@@ -194,12 +271,51 @@
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>
@@ -213,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>
@@ -232,12 +348,51 @@
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>
@@ -251,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>
@@ -269,12 +438,50 @@
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>
@@ -292,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>
@@ -313,12 +520,53 @@
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>
@@ -330,11 +578,27 @@
vehicleId - Id of Vehicle
</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">
<code>/api/vehicle/reminders/send</code>
@@ -349,7 +613,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/makebackup</code>
@@ -363,7 +627,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/cleanup</code>
@@ -388,4 +652,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>

View File

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

View File

@@ -47,9 +47,15 @@
<label class="form-check-label" for="automaticDecimalFormat">@translator.Translate(userLanguage, "Automatically Format Decimal Inputs")</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>
<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="useThreeDecimalGasConsumption" checked="@Model.UserConfig.UseThreeDecimalGasConsumption">

View File

@@ -34,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" />

View File

@@ -173,4 +173,5 @@
return { tab: "@userConfig.DefaultTab" };
}
bindWindowResize();
checkRecurringTaxes();
</script>

View File

@@ -183,6 +183,8 @@
<li><hr class="dropdown-divider"></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 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)"><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>

View File

@@ -67,6 +67,7 @@
<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" 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>
@@ -200,7 +201,7 @@
<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-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>
@@ -251,6 +252,8 @@
<li><hr class="context-menu-multiple dropdown-divider"></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 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')"><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>
@@ -268,5 +271,12 @@
{
@:convertGasConsumptionUnits(decodeHTMLEntities('@consumptionUnit'), decodeHTMLEntities('@preferredGasUnit'), false);
}
function getGasModelData(){
return {
distanceUnit: decodeHTMLEntities('@distanceUnit'),
consumptionUnit: decodeHTMLEntities('@consumptionUnit'),
gasCostFormat: decodeHTMLEntities('@gasCostFormat'),
gasConsumptionFormat: decodeHTMLEntities('@gasConsumptionFormat')
}
}
</script>

View File

@@ -12,6 +12,7 @@
var useKwh = Model.UseKwh;
var useHours = Model.UseHours;
var isNew = Model.GasRecord.Id == 0;
var useUnitFuelCost = userConfig.UseUnitForFuelCost;
string consumptionUnit;
string distanceUnit;
if (useKwh)
@@ -80,8 +81,8 @@
<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>

View File

@@ -6,16 +6,17 @@
var userLanguage = userConfig.UserLanguage;
}
@model ReportParameter
<div id="columnSelector">
<h2 class="swal2-title mb-2">@translator.Translate(userLanguage, "Select Columns")</h2>
<div id="columnSelector" style="max-height:50vh; overflow-y:auto;">
<ul class="list-group">
@foreach(string column in Model.VisibleColumns)
@foreach (string column in Model.VisibleColumns)
{
<li class="list-group-item text-start">
<input class="form-check-input column-default" type="checkbox" value="@column" id="visibleColumn_@column" checked>
<label class="form-check-label stretched-link" for="visibleColumn_@column">@(translator.Translate(userLanguage, column == nameof(GenericReportModel.DataType) ? "Type" : column))</label>
</li>
}
@foreach(string extraField in Model.ExtraFields)
@foreach (string extraField in Model.ExtraFields)
{
<li class="list-group-item text-start">
<input class="form-check-input column-extrafield" type="checkbox" value="@extraField" id="extraField_@extraField">
@@ -23,4 +24,32 @@
</li>
}
</ul>
</div>
</div>
<div class="mt-2 mb-2">
<ul class="list-group">
<li class="list-group-item text-center" style="cursor:pointer;" onclick="showReportAdvancedParameters()">
@translator.Translate(userLanguage, "Advanced Filters")
</li>
</ul>
</div>
<h2 class="mb-2 report-advanced-parameters d-none">@translator.Translate(userLanguage, "Filter by Tags")</h2>
<div class="text-start report-advanced-parameters d-none">
<select class="form-select mb-2" id="tagSelector">
<!option value="@TagFilter.Exclude">@translator.Translate(userLanguage, "Exclude Records with these Tags")</!option>
<!option value="@TagFilter.IncludeOnly">@translator.Translate(userLanguage, "Only Include Records with these Tags")</!option>
</select>
<select multiple id="tagSelectorInput"></select>
</div>
<h2 class="mb-2 report-advanced-parameters d-none">@translator.Translate(userLanguage, "Filter by Date Range")</h2>
<div class="text-start report-advanced-parameters d-none">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="dateRangeSelector">
<label class="form-check-label" for="dateRangeSelector">@translator.Translate(userLanguage, "Filter by Date Range")</label>
</div>
<div class="input-group">
<span class="input-group-text">@translator.Translate(userLanguage, "From")</span>
<input type="text" id="dateRangeStartDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Start Date")">
<span class="input-group-text">@translator.Translate(userLanguage, "To")</span>
<input type="text" id="dateRangeEndDate" class="form-control" placeholder="@translator.Translate(userLanguage,"End Date")">
</div>
</div>

View File

@@ -181,6 +181,8 @@
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ServiceRecord')"><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, 'ServiceRecord')"><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, 'ServiceRecord')"><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 text-danger" href="#" onclick="deleteRecords(selectedRow, 'ServiceRecord')"><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)"><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>

View File

@@ -182,6 +182,8 @@
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'UpgradeRecord')"><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, 'UpgradeRecord')"><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, 'UpgradeRecord')"><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 text-danger" href="#" onclick="deleteRecords(selectedRow, 'UpgradeRecord')"><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)"><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>

View File

@@ -12,7 +12,19 @@
<div class="row mt-2">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
<span class="display-6 ms-5">@translator.Translate(userLanguage, "Vehicle Maintenance Report")</span>
<div class="ms-5">
<span class="display-6">
@translator.Translate(userLanguage, "Vehicle Maintenance Report")
</span>
@if (!string.IsNullOrWhiteSpace(Model.StartDate) && !string.IsNullOrWhiteSpace(Model.EndDate))
{
<br />
<span class="lead ms-2">
@($"{@translator.Translate(userLanguage, "From")} {Model.StartDate} {@translator.Translate(userLanguage, "To")} {Model.EndDate}")
</span>
}
</div>
</div>
</div>
<hr />

View File

@@ -15,18 +15,19 @@
"DisableRegistration": false,
"EnableRootUserOIDC": false,
"HideZero": false,
"AutomaticDecimalFormat": false,
"AutomaticDecimalFormat": false,
"EnableAutoReminderRefresh": false,
"EnableAutoOdometerInsert": false,
"EnableShopSupplies": false,
"EnableExtraFieldColumns": false,
"UseUKMPG": false,
"UseThreeDecimalGasCost": true,
"UseThreeDecimalGasConsumption": true,
"UseThreeDecimalGasConsumption": true,
"UseMarkDownOnSavedNotes": false,
"HideSoldVehicles": false,
"PreferredGasMileageUnit": "",
"UserColumnPreferences": [],
"UseUnitForFuelCost": false,
"PreferredGasUnit": "",
"UserLanguage": "en_US",
"VisibleTabs": [ 0, 1, 4, 2, 3, 6, 5, 8 ],

View File

@@ -49,9 +49,13 @@
<input type="text" id="inputFileExtensions" class="form-control">
<small class="text-body-secondary">Blank for default, * for all files</small>
</div>
<div class="form-check form-switch">
<input class="form-check-input"type="checkbox" role="switch" id="inputCustomWidgets">
<label class="form-check-label" for="inputCustomWidgets">Custom Widgets</label>
</div>
</div>
<div class="col-6">
<div class="form-group">
<div class="form-group">
<label for="inputCustomLogo">Custom Logo URL</label>
<input type="text" id="inputCustomLogo" class="form-control">
<small class="text-body-secondary">Default size: 204x48</small>
@@ -66,6 +70,10 @@
<input type="text" id="inputWebHook" class="form-control">
<small class="text-body-secondary">URL to WebHook Consumer</small>
</div>
<div class="form-check form-switch">
<input class="form-check-input"type="checkbox" role="switch" id="inputInvariantAPI">
<label class="form-check-label" for="inputInvariantAPI">Invariant API</label>
</div>
</div>
</div>
</div>
@@ -262,6 +270,12 @@ function generateConfig(){
if ($("#inputPostgres").val().trim() != ''){
windowConfig["POSTGRES_CONNECTION"]=$("#inputPostgres").val();
}
if ($("#inputCustomWidgets").is(":checked")){
windowConfig["LUBELOGGER_CUSTOM_WIDGETS"]=$('#inputCustomWidgets').is(':checked');
}
if ($("#inputInvariantAPI").is(":checked")){
windowConfig["LUBELOGGER_INVARIANT_API"]=$('#inputInvariantAPI').is(':checked');
}
if ($('#inputSmtpServer').val().trim() != ''){
windowConfig["MailConfig"] = {
EmailServer: $("#inputSmtpServer").val(),
@@ -327,6 +341,12 @@ function generateConfig(){
if ($("#inputPostgres").val().trim() != ''){
dockerConfig.push(`POSTGRES_CONNECTION="${$('#inputPostgres').val()}"`);
}
if ($("#inputCustomWidgets").is(":checked")){
dockerConfig.push(`LUBELOGGER_CUSTOM_WIDGETS="${$('#inputCustomWidgets').is(':checked')}"`);
}
if ($("#inputInvariantAPI").is(":checked")){
dockerConfig.push(`LUBELOGGER_INVARIANT_API="${$('#inputInvariantAPI').is(':checked')}"`);
}
if ($('#inputSmtpServer').val().trim() != ''){
dockerConfig.push(`MailConfig__EmailServer="${$('#inputSmtpServer').val()}"`);
dockerConfig.push(`MailConfig__EmailFrom="${$('#inputSmtpFrom').val()}"`);

View File

@@ -291,6 +291,11 @@ html {
position: absolute;
}
.table-context-menu > li > .dropdown-item:hover {
background-color: rgba(var(--bs-primary-rgb)) !important;
color: #fff !important;
}
html[data-bs-theme="dark"] .table-context-menu {
background-color: rgba(33, 37, 41, 0.7);
backdrop-filter: blur(10px);

File diff suppressed because one or more lines are too long

View File

@@ -314,7 +314,8 @@ function updateMPGLabels() {
var minLabel = $("#minFuelMileageLabel");
var maxLabel = $("#maxFuelMileageLabel");
var totalConsumedLabel = $("#totalFuelConsumedLabel");
if (averageLabel.length > 0 && minLabel.length > 0 && maxLabel.length > 0 && totalConsumedLabel.length > 0) {
var totalDistanceLabel = $("#totalDistanceLabel");
if (averageLabel.length > 0 && minLabel.length > 0 && maxLabel.length > 0 && totalConsumedLabel.length > 0 && totalDistanceLabel.length > 0) {
var rowsToAggregate = $("[data-aggregated='true']").parent(":not('.override-hide')");
var rowsUnaggregated = $("[data-aggregated='false']").parent(":not('.override-hide')");
var rowMPG = rowsToAggregate.children('[data-gas-type="fueleconomy"]').toArray().map(x => globalParseFloat(x.textContent));
@@ -323,7 +324,9 @@ function updateMPGLabels() {
var totalMilesTraveled = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="mileage"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0;
var totalGasConsumed = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0;
var totalUnaggregatedGasConsumed = rowsUnaggregated.length > 0 ? rowsUnaggregated.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0;
var totalMilesTraveledUnaggregated = rowsUnaggregated.length > 0 ? rowsUnaggregated.children('[data-gas-type="mileage"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0;
var fullGasConsumed = totalGasConsumed + totalUnaggregatedGasConsumed;
var fullDistanceTraveled = totalMilesTraveled + totalMilesTraveledUnaggregated;
if (totalGasConsumed > 0) {
var averageMPG = totalMilesTraveled / totalGasConsumed;
if (!getGlobalConfig().useMPG && $("[data-gas='fueleconomy']").attr("data-unit") != 'km/l' && averageMPG > 0) {
@@ -333,6 +336,11 @@ function updateMPGLabels() {
} else {
averageLabel.text(`${averageLabel.text().split(':')[0]}: ${globalFloatToString('0.00')}`);
}
if (fullDistanceTraveled > 0) {
totalDistanceLabel.text(`${totalDistanceLabel.text().split(':')[0]}: ${fullDistanceTraveled} ${getGasModelData().distanceUnit}`);
} else {
totalDistanceLabel.text(`${totalDistanceLabel.text().split(':')[0]}: 0 ${getGasModelData().distanceUnit}`);
}
if (fullGasConsumed > 0) {
totalConsumedLabel.text(`${totalConsumedLabel.text().split(':')[0]}: ${globalFloatToString(fullGasConsumed.toFixed(2))}`);
} else {

View File

@@ -4,6 +4,11 @@
function getAndValidateSelectedColumns() {
var reportVisibleColumns = [];
var reportExtraFields = [];
var tagFilterMode = $("#tagSelector").val();
var tagsToFilter = $("#tagSelectorInput").val();
var filterByDateRange = $("#dateRangeSelector").is(":checked");
var startDate = $("#dateRangeStartDate").val();
var endDate = $("#dateRangeEndDate").val();
$("#columnSelector :checked").map(function () {
if ($(this).hasClass('column-default')) {
reportVisibleColumns.push(this.value);
@@ -11,17 +16,45 @@ function getAndValidateSelectedColumns() {
reportExtraFields.push(this.value);
}
});
var hasValidationError = false;
var validationErrorMessage = "";
if (reportVisibleColumns.length + reportExtraFields.length == 0) {
hasValidationError = true;
validationErrorMessage = "You must select at least one column";
}
if (filterByDateRange) {
//validate date range
let startDateTicks = $("#dateRangeStartDate").datepicker('getDate')?.getTime();
let endDateTicks = $("#dateRangeEndDate").datepicker('getDate')?.getTime();
if (!startDateTicks || !endDateTicks || startDateTicks > endDateTicks) {
hasValidationError = true;
validationErrorMessage = "Invalid date range";
}
}
if (hasValidationError) {
return {
hasError: true,
errorMessage: validationErrorMessage,
visibleColumns: [],
extraFields: []
extraFields: [],
tagFilter: tagFilterMode,
tags: [],
filterByDateRange: filterByDateRange,
startDate: '',
endDate: ''
}
} else {
return {
hasError: false,
errorMessage: '',
visibleColumns: reportVisibleColumns,
extraFields: reportExtraFields
extraFields: reportExtraFields,
tagFilter: tagFilterMode,
tags: tagsToFilter,
filterByDateRange: filterByDateRange,
startDate: startDate,
endDate: endDate
}
}
}
@@ -36,10 +69,17 @@ function getSavedReportParameters() {
//load selected checkboxes
selectedReportColumns.extraFields.map(x => {
$(`[value='${x}'].column-extrafield`).prop('checked', true);
})
});
selectedReportColumns.visibleColumns.map(x => {
$(`[value='${x}'].column-default`).prop('checked', true);
})
});
$("#tagSelector").val(selectedReportColumns.tagFilter);
selectedReportColumns.tags.map(x => {
$("#tagSelectorInput").append(`<option value='${x}'>${x}</option>`)
});
$("#dateRangeSelector").prop('checked', selectedReportColumns.filterByDateRange);
$("#dateRangeStartDate").val(selectedReportColumns.startDate);
$("#dateRangeEndDate").val(selectedReportColumns.endDate);
}
}
function generateVehicleHistoryReport() {
@@ -48,7 +88,6 @@ function generateVehicleHistoryReport() {
if (data) {
//prompt user to select columns
Swal.fire({
title: 'Select Columns',
html: data,
confirmButtonText: 'Generate Report',
focusConfirm: false,
@@ -56,12 +95,15 @@ function generateVehicleHistoryReport() {
//validate
var selectedColumnsData = getAndValidateSelectedColumns();
if (selectedColumnsData.hasError) {
Swal.showValidationMessage(`You must select at least one column`);
Swal.showValidationMessage(selectedColumnsData.errorMessage);
}
return { selectedColumnsData }
},
didOpen: () => {
getSavedReportParameters();
initTagSelector($("#tagSelectorInput"));
initDatePicker($('#dateRangeStartDate'));
initDatePicker($('#dateRangeEndDate'));
}
}).then(function (result) {
if (result.isConfirmed) {
@@ -415,4 +457,12 @@ function loadCustomWidgets() {
}
function hideCustomWidgetsModal() {
$("#vehicleCustomWidgetsModal").modal('hide');
}
function showReportAdvancedParameters() {
if ($(".report-advanced-parameters").hasClass("d-none")) {
$(".report-advanced-parameters").removeClass("d-none");
} else {
$(".report-advanced-parameters").addClass("d-none");
}
}

View File

@@ -67,6 +67,7 @@ function updateSettings() {
preferredGasUnit: $("#preferredGasUnit").val(),
preferredGasMileageUnit: $("#preferredFuelMileageUnit").val(),
userLanguage: $("#defaultLanguage").val(),
useUnitForFuelCost: $("#useUnitForFuelCost").is(":checked"),
visibleTabs: visibleTabs,
defaultTab: defaultTab,
tabOrder: tabOrder,

View File

@@ -28,6 +28,21 @@ function errorToast(message) {
}
})
}
function infoToast(message) {
Swal.fire({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 3000,
title: message,
timerProgressBar: true,
icon: "info",
didOpen: (toast) => {
toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer;
}
})
}
function viewVehicle(vehicleId) {
window.location.href = `/Vehicle/Index?vehicleId=${vehicleId}`;
}
@@ -936,6 +951,55 @@ function duplicateRecordsToOtherVehicles(ids, source) {
}
})
}
function insertOdometer(ids, source) {
if (ids.length == 0) {
return;
}
$("#workAroundInput").show();
var friendlySource = "";
var refreshDataCallBack;
var recordVerbiage = ids.length > 1 ? `these ${ids.length} records` : "this record";
switch (source) {
case "ServiceRecord":
friendlySource = "Service Records";
refreshDataCallBack = getVehicleServiceRecords;
break;
case "RepairRecord":
friendlySource = "Repairs";
refreshDataCallBack = getVehicleCollisionRecords;
break;
case "UpgradeRecord":
friendlySource = "Upgrades";
refreshDataCallBack = getVehicleUpgradeRecords;
break;
case "GasRecord":
friendlySource = "Fuel Records";
refreshDataCallBack = getVehicleGasRecords;
break;
}
Swal.fire({
title: "Create Odometer Records?",
text: `Create Odometer Records based on ${recordVerbiage}?`,
showCancelButton: true,
confirmButtonText: "Create",
confirmButtonColor: "#dc3545"
}).then((result) => {
if (result.isConfirmed) {
$.post('/Vehicle/BulkCreateOdometerRecords', { recordIds: ids, importMode: source }, function (data) {
if (data) {
successToast(`${ids.length} Odometer Record(s) Created`);
var vehicleId = GetVehicleId().vehicleId;
refreshDataCallBack(vehicleId);
} else {
errorToast(genericErrorMessage());
}
});
} else {
$("#workAroundInput").hide();
}
});
}
var selectedRow = [];
var isDragging = false;
$(window).on('mouseup', function (e) {
@@ -992,12 +1056,12 @@ function stopEvent() {
event.stopPropagation();
}
function rangeMouseUp(e) {
if ($(".table-context-menu").length > 0) {
$(".table-context-menu").fadeOut("fast");
}
if (isRightClick(e)) {
return;
}
if ($(".table-context-menu").length > 0) {
$(".table-context-menu").fadeOut("fast");
}
isDragging = false;
document.documentElement.onselectstart = function () { return true; };
}

View File

@@ -178,4 +178,14 @@ function getAndValidateTaxRecordValues() {
extraFields: extraFields.extraFields,
reminderRecordId: recurringReminderRecordId
}
}
function checkRecurringTaxes() {
let vehicleId = GetVehicleId().vehicleId
$.post('/Vehicle/CheckRecurringTaxRecords', { vehicleId: vehicleId }, function (data) {
if (data) {
//notify users that recurring tax records were updated and they should refresh the page to see the new changes.
infoToast(`Recurring Tax Records Updated!<br /><br /><a class='text-link' style='cursor:pointer;' onclick='viewVehicle(${vehicleId})'>Refresh to see new records</a>`);
}
})
}