Merge pull request #1189 from hargata/Hargata/413

Hargata/413
This commit is contained in:
Hargata Softworks 2026-01-02 11:28:24 -07:00 committed by GitHub
commit 550744b985
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 1726 additions and 28 deletions

View File

@ -26,10 +26,12 @@ namespace CarCareTracker.Controllers
private readonly IPlanRecordTemplateDataAccess _planRecordTemplateDataAccess;
private readonly IInspectionRecordDataAccess _inspectionRecordDataAccess;
private readonly IInspectionRecordTemplateDataAccess _inspectionRecordTemplateDataAccess;
private readonly IEquipmentRecordDataAccess _equipmentRecordDataAccess;
private readonly IUserAccessDataAccess _userAccessDataAccess;
private readonly IUserRecordDataAccess _userRecordDataAccess;
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
private readonly IReminderHelper _reminderHelper;
private readonly IEquipmentHelper _equipmentHelper;
private readonly IGasHelper _gasHelper;
private readonly IUserLogic _userLogic;
private readonly IVehicleLogic _vehicleLogic;
@ -41,6 +43,7 @@ namespace CarCareTracker.Controllers
private readonly IHttpClientFactory _httpClientFactory;
public APIController(IVehicleDataAccess dataAccess,
IGasHelper gasHelper,
IEquipmentHelper equipmentHelper,
IReminderHelper reminderHelper,
INoteDataAccess noteDataAccess,
IServiceRecordDataAccess serviceRecordDataAccess,
@ -55,6 +58,7 @@ namespace CarCareTracker.Controllers
IPlanRecordTemplateDataAccess planRecordTemplateDataAccess,
IInspectionRecordDataAccess inspectionRecordDataAccess,
IInspectionRecordTemplateDataAccess inspectionRecordTemplateDataAccess,
IEquipmentRecordDataAccess equipmentRecordDataAccess,
IUserAccessDataAccess userAccessDataAccess,
IUserRecordDataAccess userRecordDataAccess,
IExtraFieldDataAccess extraFieldDataAccess,
@ -81,11 +85,13 @@ namespace CarCareTracker.Controllers
_planRecordTemplateDataAccess = planRecordTemplateDataAccess;
_inspectionRecordDataAccess = inspectionRecordDataAccess;
_inspectionRecordTemplateDataAccess = inspectionRecordTemplateDataAccess;
_equipmentRecordDataAccess = equipmentRecordDataAccess;
_userAccessDataAccess = userAccessDataAccess;
_userRecordDataAccess = userRecordDataAccess;
_extraFieldDataAccess = extraFieldDataAccess;
_mailHelper = mailHelper;
_gasHelper = gasHelper;
_equipmentHelper = equipmentHelper;
_reminderHelper = reminderHelper;
_userLogic = userLogic;
_odometerLogic = odometerLogic;
@ -1683,7 +1689,7 @@ namespace CarCareTracker.Controllers
var tagsFilter = parameters.Tags.Split(' ').Distinct();
vehicleRecords.RemoveAll(x => !x.Tags.Any(y => tagsFilter.Contains(y)));
}
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) });
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), EquipmentRecordId = string.Join(' ', x.EquipmentRecordId) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
@ -1727,7 +1733,7 @@ namespace CarCareTracker.Controllers
var tagsFilter = parameters.Tags.Split(' ').Distinct();
vehicleRecords.RemoveAll(x => !x.Tags.Any(y => tagsFilter.Contains(y)));
}
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) });
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), EquipmentRecordId = string.Join(' ', x.EquipmentRecordId) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
@ -1766,6 +1772,36 @@ namespace CarCareTracker.Controllers
{
input.ExtraFields = new List<ExtraField>();
}
var equipmentRecordId = new List<int>();
//validate equipment record ids
if (!string.IsNullOrWhiteSpace(input.EquipmentRecordId))
{
var equipmentRecords = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(vehicleId);
if (!equipmentRecords.Any())
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed($"Input object invalid, equipment with ids {input.EquipmentRecordId} does not exist."));
}
var equipmentRecordIds = input.EquipmentRecordId.Split(' ').Distinct().ToList();
foreach(string equipmentRecordIdToAdd in equipmentRecordIds)
{
if (int.TryParse(equipmentRecordIdToAdd, out int cleanEquipmentRecordId))
{
equipmentRecordId.Add(cleanEquipmentRecordId);
} else
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed($"Input object invalid, equipment id {cleanEquipmentRecordId} is not valid."));
}
}
var equipmentRecordIdsToCompare = equipmentRecords.Select(x => x.Id);
var invalidEquipmentRecordIds = equipmentRecordId.Where(x => !equipmentRecordIdsToCompare.Contains(x));
if (invalidEquipmentRecordIds.Any())
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed($"Input object invalid, equipment with ids {string.Join(' ', invalidEquipmentRecordIds)} does not exist."));
}
}
try
{
var odometerRecord = new OdometerRecord()
@ -1777,7 +1813,8 @@ namespace CarCareTracker.Controllers
Mileage = int.Parse(input.Odometer),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList(),
EquipmentRecordId = equipmentRecordId
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(odometerRecord, "odometerrecord.add.api", User.Identity.Name));
@ -1847,6 +1884,37 @@ namespace CarCareTracker.Controllers
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
var equipmentRecordId = new List<int>();
//validate equipment record ids
if (!string.IsNullOrWhiteSpace(input.EquipmentRecordId))
{
var equipmentRecords = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(existingRecord.VehicleId);
if (!equipmentRecords.Any())
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed($"Input object invalid, equipment with ids {input.EquipmentRecordId} does not exist."));
}
var equipmentRecordIds = input.EquipmentRecordId.Split(' ').Distinct().ToList();
foreach (string equipmentRecordIdToAdd in equipmentRecordIds)
{
if (int.TryParse(equipmentRecordIdToAdd, out int cleanEquipmentRecordId))
{
equipmentRecordId.Add(cleanEquipmentRecordId);
}
else
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed($"Input object invalid, equipment id {cleanEquipmentRecordId} is not valid."));
}
}
var equipmentRecordIdsToCompare = equipmentRecords.Select(x => x.Id);
var invalidEquipmentRecordIds = equipmentRecordId.Where(x => !equipmentRecordIdsToCompare.Contains(x));
if (invalidEquipmentRecordIds.Any())
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed($"Input object invalid, equipment with ids {string.Join(' ', invalidEquipmentRecordIds)} does not exist."));
}
}
existingRecord.Date = DateTime.Parse(input.Date);
existingRecord.Mileage = int.Parse(input.Odometer);
existingRecord.InitialMileage = int.Parse(input.InitialOdometer);
@ -1854,6 +1922,7 @@ namespace CarCareTracker.Controllers
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Files = input.Files;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
existingRecord.EquipmentRecordId = equipmentRecordId;
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(existingRecord, "odometerrecord.update.api", User.Identity.Name));
}
@ -2697,6 +2766,221 @@ namespace CarCareTracker.Controllers
return File(calendarContent, "text/calendar");
}
#endregion
#region EquipmentRecord
[HttpGet]
[Route("/api/vehicle/equipmentrecords/all")]
public IActionResult AllEquipmentRecords(MethodParameter parameters)
{
List<int> vehicleIds = new List<int>();
var vehicles = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
vehicles = _userLogic.FilterUserVehicles(vehicles, GetUserID());
}
vehicleIds.AddRange(vehicles.Select(x => x.Id));
List<EquipmentRecordViewModel> vehicleRecords = new List<EquipmentRecordViewModel>();
foreach (int vehicleId in vehicleIds)
{
var equipmentRecords = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(vehicleId);
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
var convertedRecords = _equipmentHelper.GetEquipmentRecordViewModels(equipmentRecords, odometerRecords);
vehicleRecords.AddRange(convertedRecords);
}
if (parameters.Id != default)
{
vehicleRecords.RemoveAll(x => x.Id != parameters.Id);
}
if (!string.IsNullOrWhiteSpace(parameters.Tags))
{
var tagsFilter = parameters.Tags.Split(' ').Distinct();
vehicleRecords.RemoveAll(x => !x.Tags.Any(y => tagsFilter.Contains(y)));
}
var result = vehicleRecords.Select(x => new EquipmentRecordAPIExportModel { Id = x.Id.ToString(), Description = x.Description, IsEquipped = x.IsEquipped.ToString(), DistanceTraveled = x.DistanceTraveled.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))]
[HttpGet]
[Route("/api/vehicle/equipmentrecords")]
public IActionResult EquipmentRecords(int vehicleId, MethodParameter parameters)
{
if (vehicleId == default)
{
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
var vehicleRecords = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(vehicleId);
if (parameters.Id != default)
{
vehicleRecords.RemoveAll(x => x.Id != parameters.Id);
}
if (!string.IsNullOrWhiteSpace(parameters.Tags))
{
var tagsFilter = parameters.Tags.Split(' ').Distinct();
vehicleRecords.RemoveAll(x => !x.Tags.Any(y => tagsFilter.Contains(y)));
}
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
var convertedRecords = _equipmentHelper.GetEquipmentRecordViewModels(vehicleRecords, odometerRecords);
var result = convertedRecords.Select(x => new EquipmentRecordAPIExportModel { Id = x.Id.ToString(), Description = x.Description, IsEquipped = x.IsEquipped.ToString(), DistanceTraveled = x.DistanceTraveled.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), Arguments = new object[] { false, true, HouseholdPermission.Edit })]
[HttpPost]
[Route("/api/vehicle/equipmentrecords/add")]
[Consumes("application/json")]
public IActionResult AddEquipmentRecordJson(int vehicleId, [FromBody] EquipmentRecordExportModel input) => AddEquipmentRecord(vehicleId, input);
[TypeFilter(typeof(CollaboratorFilter), Arguments = new object[] { false, true, HouseholdPermission.Edit })]
[HttpPost]
[Route("/api/vehicle/equipmentrecords/add")]
public IActionResult AddEquipmentRecord(int vehicleId, EquipmentRecordExportModel input)
{
if (vehicleId == default)
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.IsEquipped))
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Input object invalid, Description and IsEquipped cannot be empty."));
}
if (input.Files == null)
{
input.Files = new List<UploadedFiles>();
}
if (input.ExtraFields == null)
{
input.ExtraFields = new List<ExtraField>();
}
try
{
var equipmentRecord = new EquipmentRecord()
{
VehicleId = vehicleId,
Description = input.Description,
IsEquipped = bool.Parse(input.IsEquipped),
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_equipmentRecordDataAccess.SaveEquipmentRecordToVehicle(equipmentRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromEquipmentRecord(equipmentRecord, "equipmentrecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Equipment Record Added", new { recordId = equipmentRecord.Id }));
}
catch (Exception ex)
{
Response.StatusCode = 500;
return Json(OperationResponse.Failed(ex.Message));
}
}
[HttpDelete]
[Route("/api/vehicle/equipmentrecords/delete")]
public IActionResult DeleteEquipmentRecord(int id)
{
var existingRecord = _equipmentRecordDataAccess.GetEquipmentRecordById(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, HouseholdPermission.Delete))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
//delete link to odometer record
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(existingRecord.VehicleId);
var linkedOdometerRecords = odometerRecords.Where(x => x.EquipmentRecordId.Contains(existingRecord.Id));
if (linkedOdometerRecords.Any())
{
foreach (OdometerRecord linkedOdometerRecord in linkedOdometerRecords)
{
linkedOdometerRecord.EquipmentRecordId.RemoveAll(x => x == existingRecord.Id);
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(linkedOdometerRecord);
}
}
var result = _equipmentRecordDataAccess.DeleteEquipmentRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromEquipmentRecord(existingRecord, "equipmentrecord.delete.api", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, "Equipment Record Deleted"));
}
[HttpPut]
[Route("/api/vehicle/equipmentrecords/update")]
[Consumes("application/json")]
public IActionResult UpdateEquipmentRecordJson([FromBody] EquipmentRecordExportModel input) => UpdateEquipmentRecord(input);
[HttpPut]
[Route("/api/vehicle/equipmentrecords/update")]
public IActionResult UpdateEquipmentRecord(EquipmentRecordExportModel input)
{
if (string.IsNullOrWhiteSpace(input.Id) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.IsEquipped))
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Input object invalid, Id, Description, and IsEquipped cannot be empty."));
}
if (input.Files == null)
{
input.Files = new List<UploadedFiles>();
}
if (input.ExtraFields == null)
{
input.ExtraFields = new List<ExtraField>();
}
try
{
//retrieve existing record
var existingRecord = _equipmentRecordDataAccess.GetEquipmentRecordById(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, HouseholdPermission.Edit))
{
Response.StatusCode = 401;
return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle."));
}
existingRecord.Description = input.Description;
existingRecord.IsEquipped = bool.Parse(input.IsEquipped);
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();
_equipmentRecordDataAccess.SaveEquipmentRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromEquipmentRecord(existingRecord, "equipmentrecord.update.api", User.Identity.Name));
}
else
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Invalid Record Id"));
}
return Json(OperationResponse.Succeed("Equipment Record Updated"));
}
catch (Exception ex)
{
Response.StatusCode = 500;
return Json(OperationResponse.Failed(ex.Message));
}
}
#endregion
[HttpPost]
[Route("/api/documents/upload")]
public IActionResult UploadDocument(List<IFormFile> documents)
@ -2927,6 +3211,7 @@ namespace CarCareTracker.Controllers
vehicleDocuments.AddRange(_planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_inspectionRecordDataAccess.GetInspectionRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_inspectionRecordTemplateDataAccess.GetInspectionRecordTemplatesByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
}
//shop supplies
vehicleDocuments.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(0).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));

View File

@ -55,6 +55,7 @@ namespace CarCareTracker.Controllers
"CREATE TABLE IF NOT EXISTS app.extrafields (id INT primary key, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.inspectionrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.inspectionrecordtemplates (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.equipmentrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.userhouseholdrecords (parentUserId INT, childUserId INT, data jsonb not null, PRIMARY KEY(parentUserId, childUserId))"
};
foreach(string cmd in cmds)
@ -104,6 +105,8 @@ namespace CarCareTracker.Controllers
var extrafields = new List<RecordExtraField>();
var inspectionrecords = new List<InspectionRecord>();
var inspectionrecordtemplates = new List<InspectionRecordInput>();
var equipmentrecords = new List<EquipmentRecord>();
var userhouseholdrecords = new List<UserHousehold>();
#region "Part1"
string cmd = $"SELECT data FROM app.vehicles";
@ -457,6 +460,26 @@ namespace CarCareTracker.Controllers
table.Upsert(record);
};
}
cmd = $"SELECT data FROM app.equipmentrecords";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
equipmentrecords.Add(JsonSerializer.Deserialize<EquipmentRecord>(reader["data"] as string));
}
}
foreach (var record in equipmentrecords)
{
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<EquipmentRecord>("equipmentrecords");
table.Upsert(record);
}
;
}
#endregion
#region "Part6"
cmd = $"SELECT data FROM app.userhouseholdrecords";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
@ -526,6 +549,8 @@ namespace CarCareTracker.Controllers
var extrafields = new List<RecordExtraField>();
var inspectionrecords = new List<InspectionRecord>();
var inspectionrecordtemplates = new List<InspectionRecordInput>();
var equipmentrecords = new List<EquipmentRecord>();
var userhouseholdrecords = new List<UserHousehold>();
#region "Part1"
using (var db = new LiteDatabase(fullFileName))
@ -839,6 +864,25 @@ namespace CarCareTracker.Controllers
}
}
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<EquipmentRecord>("equipmentrecords");
equipmentrecords = table.FindAll().ToList();
}
;
foreach (var record in equipmentrecords)
{
string cmd = $"INSERT INTO app.equipmentrecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.equipmentrecords_id_seq', (SELECT MAX(id) from app.equipmentrecords));";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
#endregion
#region "Part6"
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<UserHousehold>("userhouseholdrecords");
userhouseholdrecords = table.FindAll().ToList();

View File

@ -0,0 +1,113 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetEquipmentRecordsByVehicleId(int vehicleId)
{
var result = new List<EquipmentRecordViewModel>();
var equipmentRecords = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(vehicleId);
//convert to viewmodel and calculate sum of distance traveled
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
result = _equipmentHelper.GetEquipmentRecordViewModels(equipmentRecords, odometerRecords);
result = result.OrderByDescending(x => x.IsEquipped).ThenBy(x=>x.Description).ToList();
return PartialView("Equipment/_EquipmentRecords", result);
}
[HttpPost]
public IActionResult SaveEquipmentRecordToVehicleId(EquipmentRecordInput equipmentRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), equipmentRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//move files from temp.
equipmentRecord.Files = equipmentRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var convertedRecord = equipmentRecord.ToEquipmentRecord();
var result = _equipmentRecordDataAccess.SaveEquipmentRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromEquipmentRecord(convertedRecord, equipmentRecord.Id == default ? "equipmentrecord.add" : "equipmentrecord.update", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpGet]
public IActionResult GetAddEquipmentRecordPartialView()
{
return PartialView("Equipment/_EquipmentRecordModal", new EquipmentRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.EquipmentRecord).ExtraFields });
}
[HttpGet]
public IActionResult GetEquipmentRecordForEditById(int equipmentRecordId)
{
var result = _equipmentRecordDataAccess.GetEquipmentRecordById(equipmentRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(result.VehicleId);
var linkedOdometerRecords = odometerRecords.Where(x => x.EquipmentRecordId.Contains(equipmentRecordId));
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
linkedOdometerRecords = linkedOdometerRecords.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
}
else
{
linkedOdometerRecords = linkedOdometerRecords.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
}
//convert to Input object.
var convertedResult = new EquipmentRecordInput
{
Id = result.Id,
Description = result.Description,
IsEquipped = result.IsEquipped,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
OdometerRecords = linkedOdometerRecords.ToList(),
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.EquipmentRecord).ExtraFields)
};
return PartialView("Equipment/_EquipmentRecordModal", convertedResult);
}
private OperationResponse DeleteEquipmentRecordWithChecks(int equipmentRecordId)
{
var existingRecord = _equipmentRecordDataAccess.GetEquipmentRecordById(equipmentRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
//delete link to odometer record
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(existingRecord.VehicleId);
var linkedOdometerRecords = odometerRecords.Where(x => x.EquipmentRecordId.Contains(equipmentRecordId));
if (linkedOdometerRecords.Any())
{
foreach(OdometerRecord linkedOdometerRecord in linkedOdometerRecords)
{
linkedOdometerRecord.EquipmentRecordId.RemoveAll(x => x == equipmentRecordId);
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(linkedOdometerRecord);
}
}
var result = _equipmentRecordDataAccess.DeleteEquipmentRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromEquipmentRecord(existingRecord, "equipmentrecord.delete", User.Identity.Name));
}
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
[HttpPost]
public IActionResult DeleteEquipmentRecordById(int equipmentRecordId)
{
var result = DeleteEquipmentRecordWithChecks(equipmentRecordId);
return Json(result);
}
}
}

View File

@ -165,6 +165,26 @@ namespace CarCareTracker.Controllers
}
}
break;
case ImportMode.EquipmentRecord:
{
var exportData = new List<EquipmentRecordExportModel> { new EquipmentRecordExportModel
{
Description = "Test",
Notes = "Test Note",
Tags = "test1 test2",
IsEquipped = true.ToString()
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteEquipmentRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
default:
return Json(OperationResponse.Failed("No parameters"));
}
@ -253,7 +273,7 @@ namespace CarCareTracker.Controllers
writer.Dispose();
}
return Json($"/{fileNameToExport}");
}
}
else
{
return Json(OperationResponse.Failed("No Records"));
@ -555,6 +575,33 @@ namespace CarCareTracker.Controllers
return Json(OperationResponse.Failed("No Records"));
}
}
else if (mode == ImportMode.EquipmentRecord)
{
var vehicleRecords = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new EquipmentRecordExportModel
{
Description = x.Description,
Tags = string.Join(" ", x.Tags),
Notes = x.Notes,
IsEquipped = x.IsEquipped.ToString(),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteEquipmentRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
else
{
return Json(OperationResponse.Failed("No Records"));
}
}
else if (mode == ImportMode.GasRecord)
{
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
@ -830,6 +877,19 @@ namespace CarCareTracker.Controllers
});
}
}
else if (mode == ImportMode.EquipmentRecord)
{
var convertedRecord = new EquipmentRecord()
{
VehicleId = vehicleId,
Description = importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
IsEquipped = bool.Parse(importModel.IsEquipped),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_equipmentRecordDataAccess.SaveEquipmentRecordToVehicle(convertedRecord);
}
else if (mode == ImportMode.SupplyRecord)
{
var convertedRecord = new SupplyRecord()

View File

@ -62,13 +62,26 @@ namespace CarCareTracker.Controllers
[HttpGet]
public IActionResult GetAddOdometerRecordPartialView(int vehicleId)
{
return PartialView("Odometer/_OdometerRecordModal", new OdometerRecordInput() { InitialMileage = _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()), ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields });
return PartialView("Odometer/_OdometerRecordModal", new OdometerRecordInput() {
InitialMileage = _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()),
ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields,
EquipmentRecords = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(vehicleId).OrderByDescending(x => x.IsEquipped).ThenBy(x => x.Description).ToList()
});
}
[HttpPost]
public IActionResult GetOdometerRecordsEditModal(List<int> recordIds)
public IActionResult GetOdometerRecordsEditModal(List<int> recordIds, int vehicleId)
{
var extraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields;
return PartialView("Odometer/_OdometerRecordsModal", new OdometerRecordEditModel { RecordIds = recordIds, EditRecord = new OdometerRecord { ExtraFields = extraFields } });
var equipmentRecords = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(vehicleId).OrderByDescending(x => x.IsEquipped).ThenBy(x => x.Description).ToList();
foreach(EquipmentRecord equipmentRecord in equipmentRecords)
{
equipmentRecord.IsEquipped = false;
}
return PartialView("Odometer/_OdometerRecordsModal", new OdometerRecordEditModel {
RecordIds = recordIds,
EditRecord = new OdometerRecord { ExtraFields = extraFields },
EquipmentRecords = equipmentRecords
});
}
[HttpPost]
public IActionResult SaveMultipleOdometerRecords(OdometerRecordEditModel editModel)
@ -79,6 +92,7 @@ namespace CarCareTracker.Controllers
var noteIsEdited = !string.IsNullOrWhiteSpace(editModel.EditRecord.Notes);
var tagsIsEdited = editModel.EditRecord.Tags.Any();
var extraFieldIsEdited = editModel.EditRecord.ExtraFields.Any();
var equipmentIsEdited = editModel.EditEquipment;
//handle clear overrides
if (tagsIsEdited && editModel.EditRecord.Tags.Contains("---"))
{
@ -133,6 +147,10 @@ namespace CarCareTracker.Controllers
}
}
}
if (equipmentIsEdited)
{
existingRecord.EquipmentRecordId = editModel.EditRecord.EquipmentRecordId;
}
result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(existingRecord);
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
@ -146,6 +164,12 @@ namespace CarCareTracker.Controllers
{
return Redirect("/Error/Unauthorized");
}
//check for equipment
var equipmentRecords = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(result.VehicleId).OrderByDescending(x => x.IsEquipped).ThenBy(x => x.Description).ToList();
foreach(EquipmentRecord equipmentRecord in equipmentRecords)
{
equipmentRecord.IsEquipped = result.EquipmentRecordId.Contains(equipmentRecord.Id);
}
//convert to Input object.
var convertedResult = new OdometerRecordInput
{
@ -157,7 +181,8 @@ namespace CarCareTracker.Controllers
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields)
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields),
EquipmentRecords = equipmentRecords
};
return PartialView("Odometer/_OdometerRecordModal", convertedResult);
}

View File

@ -29,10 +29,12 @@ namespace CarCareTracker.Controllers
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
private readonly IInspectionRecordDataAccess _inspectionRecordDataAccess;
private readonly IInspectionRecordTemplateDataAccess _inspectionRecordTemplateDataAccess;
private readonly IEquipmentRecordDataAccess _equipmentRecordDataAccess;
private readonly IWebHostEnvironment _webEnv;
private readonly IConfigHelper _config;
private readonly IFileHelper _fileHelper;
private readonly IGasHelper _gasHelper;
private readonly IEquipmentHelper _equipmentHelper;
private readonly IReminderHelper _reminderHelper;
private readonly IReportHelper _reportHelper;
private readonly IUserLogic _userLogic;
@ -43,6 +45,7 @@ namespace CarCareTracker.Controllers
public VehicleController(ILogger<VehicleController> logger,
IFileHelper fileHelper,
IGasHelper gasHelper,
IEquipmentHelper equipmentHelper,
IReminderHelper reminderHelper,
IReportHelper reportHelper,
IVehicleDataAccess dataAccess,
@ -64,13 +67,15 @@ namespace CarCareTracker.Controllers
IWebHostEnvironment webEnv,
IConfigHelper config,
IInspectionRecordDataAccess inspectionRecordDataAccess,
IInspectionRecordTemplateDataAccess inspectionRecordTemplateDataAccess)
IInspectionRecordTemplateDataAccess inspectionRecordTemplateDataAccess,
IEquipmentRecordDataAccess equipmentRecordDataAccess)
{
_logger = logger;
_dataAccess = dataAccess;
_noteDataAccess = noteDataAccess;
_fileHelper = fileHelper;
_gasHelper = gasHelper;
_equipmentHelper = equipmentHelper;
_reminderHelper = reminderHelper;
_reportHelper = reportHelper;
_serviceRecordDataAccess = serviceRecordDataAccess;
@ -84,13 +89,14 @@ namespace CarCareTracker.Controllers
_planRecordTemplateDataAccess = planRecordTemplateDataAccess;
_inspectionRecordDataAccess = inspectionRecordDataAccess;
_inspectionRecordTemplateDataAccess = inspectionRecordTemplateDataAccess;
_equipmentRecordDataAccess = equipmentRecordDataAccess;
_odometerRecordDataAccess = odometerRecordDataAccess;
_extraFieldDataAccess = extraFieldDataAccess;
_userLogic = userLogic;
_odometerLogic = odometerLogic;
_vehicleLogic = vehicleLogic;
_webEnv = webEnv;
_config = config;
_config = config;
}
private int GetUserID()
{
@ -167,6 +173,7 @@ namespace CarCareTracker.Controllers
_planRecordTemplateDataAccess.DeleteAllPlanRecordTemplatesByVehicleId(vehicleId) &&
_inspectionRecordDataAccess.DeleteAllInspectionRecordsByVehicleId(vehicleId) &&
_inspectionRecordTemplateDataAccess.DeleteAllInspectionReportTemplatesByVehicleId(vehicleId) &&
_equipmentRecordDataAccess.DeleteAllEquipmentRecordsByVehicleId(vehicleId) &&
_supplyRecordDataAccess.DeleteAllSupplyRecordsByVehicleId(vehicleId) &&
_odometerRecordDataAccess.DeleteAllOdometerRecordsByVehicleId(vehicleId) &&
_userLogic.DeleteAllAccessToVehicle(vehicleId) &&
@ -196,6 +203,7 @@ namespace CarCareTracker.Controllers
_planRecordTemplateDataAccess.DeleteAllPlanRecordTemplatesByVehicleId(vehicleId) &&
_inspectionRecordDataAccess.DeleteAllInspectionRecordsByVehicleId(vehicleId) &&
_inspectionRecordTemplateDataAccess.DeleteAllInspectionReportTemplatesByVehicleId(vehicleId) &&
_equipmentRecordDataAccess.DeleteAllEquipmentRecordsByVehicleId(vehicleId) &&
_supplyRecordDataAccess.DeleteAllSupplyRecordsByVehicleId(vehicleId) &&
_odometerRecordDataAccess.DeleteAllOdometerRecordsByVehicleId(vehicleId) &&
_userLogic.DeleteAllAccessToVehicle(vehicleId) &&
@ -449,6 +457,19 @@ namespace CarCareTracker.Controllers
}
}
break;
case ImportMode.EquipmentRecord:
{
var results = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(vehicleId);
if (caseSensitive)
{
searchResults.AddRange(results.Where(x => JsonSerializer.Serialize(x, serializerOption).Contains(searchQuery)).Select(x => new SearchResult { Id = x.Id, RecordType = ImportMode.EquipmentRecord, Description = $"{x.Description}" }));
}
else
{
searchResults.AddRange(results.Where(x => JsonSerializer.Serialize(x, serializerOption).ToLower().Contains(searchQuery)).Select(x => new SearchResult { Id = x.Id, RecordType = ImportMode.EquipmentRecord, Description = $"{x.Description}" }));
}
}
break;
}
}
return PartialView("_GlobalSearchResult", searchResults);
@ -537,6 +558,13 @@ namespace CarCareTracker.Controllers
searchResults.AddRange(results.Select(x => new SearchResult { Id = x.Id, RecordType = ImportMode.InspectionRecord, Description = $"{x.Date.ToShortDateString()} - {x.Description}" }));
}
break;
case ImportMode.EquipmentRecord:
{
var results = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(vehicleId);
results.RemoveAll(x => !x.Tags.Any(y => tagsFilter.Contains(y)));
searchResults.AddRange(results.Select(x => new SearchResult { Id = x.Id, RecordType = ImportMode.EquipmentRecord, Description = $"{x.Description}" }));
}
break;
}
}
return PartialView("_MapSearchResult", searchResults);
@ -606,6 +634,11 @@ namespace CarCareTracker.Controllers
var results = _inspectionRecordDataAccess.GetInspectionRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Inspection Record Not Found"));
}
case ImportMode.EquipmentRecord:
{
var results = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Equipment Record Not Found"));
}
}
return Json(OperationResponse.Failed("Record Not Found"));
}
@ -765,6 +798,9 @@ namespace CarCareTracker.Controllers
case ImportMode.InspectionRecord:
result = DeleteInspectionRecordWithChecks(recordId);
break;
case ImportMode.EquipmentRecord:
result = DeleteEquipmentRecordWithChecks(recordId);
break;
}
}
if (result.Success)
@ -979,6 +1015,18 @@ namespace CarCareTracker.Controllers
result = _inspectionRecordTemplateDataAccess.SaveInspectionReportTemplateToVehicle(existingRecord);
}
break;
case ImportMode.EquipmentRecord:
{
var existingRecord = _equipmentRecordDataAccess.GetEquipmentRecordById(recordId);
//security check
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
existingRecord.Id = default;
result = _equipmentRecordDataAccess.SaveEquipmentRecordToVehicle(existingRecord);
}
break;
}
}
if (result)
@ -1084,6 +1132,7 @@ namespace CarCareTracker.Controllers
{
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
existingRecord.Id = default;
existingRecord.EquipmentRecordId = new List<int>();
foreach (int vehicleId in vehicleIds)
{
existingRecord.VehicleId = vehicleId;
@ -1127,6 +1176,17 @@ namespace CarCareTracker.Controllers
}
}
break;
case ImportMode.EquipmentRecord:
{
var existingRecord = _equipmentRecordDataAccess.GetEquipmentRecordById(recordId);
existingRecord.Id = default;
foreach (int vehicleId in vehicleIds)
{
existingRecord.VehicleId = vehicleId;
result = _equipmentRecordDataAccess.SaveEquipmentRecordToVehicle(existingRecord);
}
}
break;
}
}
if (result)
@ -1439,7 +1499,6 @@ namespace CarCareTracker.Controllers
stickerViewModel.GenericRecords.Add(_serviceRecordDataAccess.GetServiceRecordById(recordId));
recordsAdded++;
}
}
break;
case ImportMode.RepairRecord:
@ -1475,7 +1534,6 @@ namespace CarCareTracker.Controllers
});
recordsAdded++;
}
}
break;
case ImportMode.TaxRecord:
@ -1517,7 +1575,6 @@ namespace CarCareTracker.Controllers
});
recordsAdded++;
}
}
break;
case ImportMode.OdometerRecord:
@ -1534,7 +1591,6 @@ namespace CarCareTracker.Controllers
});
recordsAdded++;
}
}
break;
case ImportMode.ReminderRecord:
@ -1544,7 +1600,6 @@ namespace CarCareTracker.Controllers
stickerViewModel.ReminderRecords.Add(_reminderRecordDataAccess.GetReminderRecordById(recordId));
recordsAdded++;
}
}
break;
case ImportMode.PlanRecord:
@ -1573,6 +1628,15 @@ namespace CarCareTracker.Controllers
recordsAdded++;
}
break;
case ImportMode.EquipmentRecord:
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
foreach (int recordId in recordIds)
{
var record = _equipmentRecordDataAccess.GetEquipmentRecordById(recordId);
stickerViewModel.EquipmentRecords.Add(_equipmentHelper.GetEquipmentRecordStickerViewModel(record, odometerRecords));
recordsAdded++;
}
break;
}
if (recordsAdded > 0)
{

View File

@ -14,6 +14,7 @@
PlanRecord = 9,
OdometerRecord = 10,
VehicleRecord = 11,
InspectionRecord = 12
InspectionRecord = 12,
EquipmentRecord = 13
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class EquipmentRecordDataAccess : IEquipmentRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "equipmentrecords";
public EquipmentRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<EquipmentRecord> GetEquipmentRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<EquipmentRecord>(tableName);
var equipmentRecords = table.Find(Query.EQ(nameof(EquipmentRecord.VehicleId), vehicleId));
return equipmentRecords.ToList() ?? new List<EquipmentRecord>();
}
public EquipmentRecord GetEquipmentRecordById(int equipmentRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<EquipmentRecord>(tableName);
return table.FindById(equipmentRecordId);
}
public bool DeleteEquipmentRecordById(int equipmentRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<EquipmentRecord>(tableName);
table.Delete(equipmentRecordId);
db.Checkpoint();
return true;
}
public bool SaveEquipmentRecordToVehicle(EquipmentRecord equipmentRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<EquipmentRecord>(tableName);
table.Upsert(equipmentRecord);
db.Checkpoint();
return true;
}
public bool DeleteAllEquipmentRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<EquipmentRecord>(tableName);
var equipmentRecords = table.DeleteMany(Query.EQ(nameof(EquipmentRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,161 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using Npgsql;
using System.Text.Json;
namespace CarCareTracker.External.Implementations
{
public class PGEquipmentRecordDataAccess : IEquipmentRecordDataAccess
{
private NpgsqlDataSource pgDataSource;
private readonly ILogger<PGEquipmentRecordDataAccess> _logger;
private static string tableName = "equipmentrecords";
public PGEquipmentRecordDataAccess(IConfiguration config, ILogger<PGEquipmentRecordDataAccess> logger)
{
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
_logger = logger;
try
{
//create table if not exist.
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
}
public List<EquipmentRecord> GetEquipmentRecordsByVehicleId(int vehicleId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
var results = new List<EquipmentRecord>();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
EquipmentRecord equipmentRecord = JsonSerializer.Deserialize<EquipmentRecord>(reader["data"] as string);
results.Add(equipmentRecord);
}
}
return results;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new List<EquipmentRecord>();
}
}
public EquipmentRecord GetEquipmentRecordById(int equipmentRecordId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
var result = new EquipmentRecord();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", equipmentRecordId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
EquipmentRecord equipmentRecord = JsonSerializer.Deserialize<EquipmentRecord>(reader["data"] as string);
result = equipmentRecord;
}
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new EquipmentRecord();
}
}
public bool DeleteEquipmentRecordById(int equipmentRecordId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", equipmentRecordId);
return ctext.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool SaveEquipmentRecordToVehicle(EquipmentRecord equipmentRecord)
{
try
{
if (equipmentRecord.Id == default)
{
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("vehicleId", equipmentRecord.VehicleId);
ctext.Parameters.AddWithValue("data", "{}");
equipmentRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
//update json data
if (equipmentRecord.Id != default)
{
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
using (var ctextU = pgDataSource.CreateCommand(cmdU))
{
var serializedData = JsonSerializer.Serialize(equipmentRecord);
ctextU.Parameters.AddWithValue("id", equipmentRecord.Id);
ctextU.Parameters.AddWithValue("data", serializedData);
return ctextU.ExecuteNonQuery() > 0;
}
}
return equipmentRecord.Id != default;
}
}
else
{
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
var serializedData = JsonSerializer.Serialize(equipmentRecord);
ctext.Parameters.AddWithValue("id", equipmentRecord.Id);
ctext.Parameters.AddWithValue("data", serializedData);
return ctext.ExecuteNonQuery() > 0;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool DeleteAllEquipmentRecordsByVehicleId(int vehicleId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,13 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface IEquipmentRecordDataAccess
{
public List<EquipmentRecord> GetEquipmentRecordsByVehicleId(int vehicleId);
public EquipmentRecord GetEquipmentRecordById(int serviceRecordId);
public bool DeleteEquipmentRecordById(int equipmentRecordId);
public bool SaveEquipmentRecordToVehicle(EquipmentRecord serviceRecord);
public bool DeleteAllEquipmentRecordsByVehicleId(int vehicleId);
}
}

46
Helper/EquipmentHelper.cs Normal file
View File

@ -0,0 +1,46 @@
using CarCareTracker.Models;
namespace CarCareTracker.Helper
{
public interface IEquipmentHelper
{
List<EquipmentRecordViewModel> GetEquipmentRecordViewModels(List<EquipmentRecord> equipmentRecords, List<OdometerRecord> odometerRecords);
EquipmentRecordStickerViewModel GetEquipmentRecordStickerViewModel(EquipmentRecord equipmentRecord, List<OdometerRecord> odometerRecords);
}
public class EquipmentHelper : IEquipmentHelper
{
public List<EquipmentRecordViewModel> GetEquipmentRecordViewModels(List<EquipmentRecord> equipmentRecords, List<OdometerRecord> odometerRecords)
{
List<EquipmentRecordViewModel> result = new List<EquipmentRecordViewModel>();
foreach (EquipmentRecord equipmentRecord in equipmentRecords)
{
var distanceTraveled = odometerRecords.Where(x => x.EquipmentRecordId.Contains(equipmentRecord.Id)).Sum(y => y.DistanceTraveled);
result.Add(new EquipmentRecordViewModel
{
Id = equipmentRecord.Id,
DistanceTraveled = distanceTraveled,
Description = equipmentRecord.Description,
IsEquipped = equipmentRecord.IsEquipped,
VehicleId = equipmentRecord.VehicleId,
ExtraFields = equipmentRecord.ExtraFields,
Files = equipmentRecord.Files,
Notes = equipmentRecord.Notes,
Tags = equipmentRecord.Tags
});
}
return result;
}
public EquipmentRecordStickerViewModel GetEquipmentRecordStickerViewModel(EquipmentRecord equipmentRecord, List<OdometerRecord> odometerRecords)
{
var linkedOdometerRecords = odometerRecords.Where(x => x.EquipmentRecordId.Contains(equipmentRecord.Id)).ToList();
return new EquipmentRecordStickerViewModel {
Description = equipmentRecord.Description,
IsEquipped = equipmentRecord.IsEquipped,
Notes = equipmentRecord.Notes,
ExtraFields = equipmentRecord.ExtraFields,
Distance = linkedOdometerRecords.Sum(x=>x.DistanceTraveled),
OdometerRecords = linkedOdometerRecords
};
}
}
}

View File

@ -453,6 +453,8 @@ namespace CarCareTracker.Helper
return "bi-bell";
case ImportMode.InspectionRecord:
return "bi-clipboard-check";
case ImportMode.EquipmentRecord:
return "bi-disc";
default:
return "bi-file-bar-graph";
}
@ -701,6 +703,33 @@ namespace CarCareTracker.Helper
_csv.NextRecord();
}
}
public static void WriteEquipmentRecordExportModel(CsvWriter _csv, IEnumerable<EquipmentRecordExportModel> genericRecords)
{
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
//write headers
_csv.WriteField(nameof(EquipmentRecordExportModel.Description));
_csv.WriteField(nameof(EquipmentRecordExportModel.Notes));
_csv.WriteField(nameof(EquipmentRecordExportModel.Tags));
_csv.WriteField(nameof(EquipmentRecordExportModel.IsEquipped));
foreach (string extraHeader in extraHeaders)
{
_csv.WriteField($"extrafield_{extraHeader}");
}
_csv.NextRecord();
foreach (EquipmentRecordExportModel genericRecord in genericRecords)
{
_csv.WriteField(genericRecord.Description);
_csv.WriteField(genericRecord.Notes);
_csv.WriteField(genericRecord.Tags);
_csv.WriteField(genericRecord.IsEquipped);
foreach (string extraHeader in extraHeaders)
{
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
}
_csv.NextRecord();
}
}
public static void WriteAttachmentExportModel(CsvWriter _csv, IEnumerable<AttachmentExportModel> genericRecords)
{
//write headers

View File

@ -12,10 +12,12 @@ namespace CarCareTracker.Logic
public class OdometerLogic: IOdometerLogic
{
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
private readonly IEquipmentRecordDataAccess _equipmentRecordDataAccess;
private readonly ILogger<IOdometerLogic> _logger;
public OdometerLogic(IOdometerRecordDataAccess odometerRecordDataAccess, ILogger<IOdometerLogic> logger)
public OdometerLogic(IOdometerRecordDataAccess odometerRecordDataAccess, IEquipmentRecordDataAccess equipmentRecordDataAccess, ILogger<IOdometerLogic> logger)
{
_odometerRecordDataAccess = odometerRecordDataAccess;
_equipmentRecordDataAccess = equipmentRecordDataAccess;
_logger = logger;
}
public int GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords)
@ -39,7 +41,13 @@ namespace CarCareTracker.Logic
}
var lastReportedMileage = GetLastOdometerRecordMileage(odometer.VehicleId, new List<OdometerRecord>());
odometer.InitialMileage = lastReportedMileage != default ? lastReportedMileage : odometer.Mileage;
//add equipment
var equipmentRecords = _equipmentRecordDataAccess.GetEquipmentRecordsByVehicleId(odometer.VehicleId);
var equippedEquipment = equipmentRecords.Where(x => x.IsEquipped);
if (equippedEquipment.Any())
{
odometer.EquipmentRecordId = equippedEquipment.Select(x => x.Id).ToList();
}
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometer);
return result;
}

View File

@ -30,6 +30,7 @@ namespace CarCareTracker.MapProfile
Map(m => m.Type).Name(["type"]);
Map(m => m.Priority).Name(["priority"]);
Map(m => m.Tags).Name(["tags"]);
Map(m => m.IsEquipped).Name(["isequipped"]);
Map(m => m.ExtraFields).Convert(row =>
{
var attributes = new Dictionary<string, string>();

View File

@ -0,0 +1,14 @@
namespace CarCareTracker.Models
{
public class EquipmentRecord
{
public int Id { get; set; }
public int VehicleId { get; set; }
public string Description { get; set; }
public bool IsEquipped { get; set; }
public string Notes { get; set; }
public List<string> Tags { get; set; } = new List<string>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
}
}

View File

@ -0,0 +1,28 @@
namespace CarCareTracker.Models
{
public class EquipmentRecordInput
{
public int Id { get; set; }
public int VehicleId { get; set; }
public string Description { get; set; }
public bool IsEquipped { get; set; }
public string Notes { get; set; }
public List<string> Tags { get; set; } = new List<string>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<OdometerRecord> OdometerRecords { get; set; } = new List<OdometerRecord>();
public EquipmentRecord ToEquipmentRecord() {
return new EquipmentRecord
{
Id = Id,
VehicleId = VehicleId,
Description = Description,
IsEquipped = IsEquipped,
Notes = Notes,
Files = Files,
Tags = Tags,
ExtraFields = ExtraFields
};
}
}
}

View File

@ -0,0 +1,12 @@
namespace CarCareTracker.Models
{
public class EquipmentRecordStickerViewModel
{
public string Description { get; set; }
public bool IsEquipped { get; set; }
public string Notes { get; set; }
public int Distance { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<OdometerRecord> OdometerRecords { get; set; } = new List<OdometerRecord>();
}
}

View File

@ -0,0 +1,15 @@
namespace CarCareTracker.Models
{
public class EquipmentRecordViewModel
{
public int Id { get; set; }
public int VehicleId { get; set; }
public string Description { get; set; }
public bool IsEquipped { get; set; }
public string Notes { get; set; }
public List<string> Tags { get; set; } = new List<string>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public int DistanceTraveled { get; set; }
}
}

View File

@ -12,5 +12,6 @@
public List<string> Tags { get; set; } = new List<string>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<int> EquipmentRecordId { get; set; } = new List<int>();
}
}

View File

@ -4,5 +4,7 @@
{
public List<int> RecordIds { get; set; } = new List<int>();
public OdometerRecord EditRecord { get; set; } = new OdometerRecord();
public bool EditEquipment { get; set; } = false;
public List<EquipmentRecord> EquipmentRecords { get; set; } = new List<EquipmentRecord>();
}
}

View File

@ -11,6 +11,19 @@
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public OdometerRecord ToOdometerRecord() { return new OdometerRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Mileage = Mileage, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields, InitialMileage = InitialMileage }; }
public List<int> EquipmentRecordId { get; set; } = new List<int>();
public List<EquipmentRecord> EquipmentRecords { get; set; } = new List<EquipmentRecord>();
public OdometerRecord ToOdometerRecord() { return new OdometerRecord {
Id = Id,
VehicleId = VehicleId,
Date = DateTime.Parse(Date),
Mileage = Mileage,
Notes = Notes,
Files = Files,
Tags = Tags,
ExtraFields = ExtraFields,
InitialMileage = InitialMileage,
EquipmentRecordId = EquipmentRecordId
}; }
}
}

View File

@ -30,6 +30,7 @@ namespace CarCareTracker.Models
public string PartSupplier { get; set; }
public string PartQuantity { get; set; }
public string Tags { get; set; }
public string IsEquipped { get; set; }
public Dictionary<string,string> ExtraFields {get;set;}
}
@ -81,6 +82,7 @@ namespace CarCareTracker.Models
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public string EquipmentRecordId { get; set; }
}
public class TaxRecordExportModel
{
@ -119,6 +121,35 @@ namespace CarCareTracker.Models
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
public class EquipmentRecordExportModel
{
[JsonConverter(typeof(FromIntOptional))]
public string Id { get; set; }
public string Description { get; set; }
[JsonConverter(typeof(FromBoolOptional))]
public string IsEquipped { get; set; }
public string Notes { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
/// <summary>
/// Only used for the API GET Method
/// </summary>
public class EquipmentRecordAPIExportModel
{
[JsonConverter(typeof(FromIntOptional))]
public string Id { get; set; }
public string Description { get; set; }
[JsonConverter(typeof(FromBoolOptional))]
public string IsEquipped { get; set; }
[JsonConverter(typeof(FromIntOptional))]
public string DistanceTraveled { get; set; }
public string Notes { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
public class ReminderExportModel
{
[JsonConverter(typeof(FromIntOptional))]

View File

@ -8,5 +8,6 @@
public List<GenericRecord> GenericRecords { get; set; } = new List<GenericRecord>();
public List<SupplyRecord> SupplyRecords { get; set; } = new List<SupplyRecord>();
public List<InspectionRecord> InspectionRecords { get; set; } = new List<InspectionRecord>();
public List<EquipmentRecordStickerViewModel> EquipmentRecords { get; set; } = new List<EquipmentRecordStickerViewModel>();
}
}

View File

@ -168,6 +168,21 @@ namespace CarCareTracker.Models
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {taxRecord.Description}"
};
}
public static WebHookPayload FromEquipmentRecord(EquipmentRecord equipmentRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
payloadDictionary.Add("user", userName);
payloadDictionary.Add("description", equipmentRecord.Description);
payloadDictionary.Add("vehicleId", equipmentRecord.VehicleId.ToString());
return new WebHookPayload
{
Type = actionType,
Data = payloadDictionary,
VehicleId = equipmentRecord.VehicleId.ToString(),
Username = userName,
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {equipmentRecord.Description}"
};
}
public static WebHookPayload FromPlanRecord(PlanRecord planRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();

View File

@ -52,6 +52,7 @@
ImportMode.TaxRecord,
ImportMode.NoteRecord,
ImportMode.InspectionRecord,
ImportMode.EquipmentRecord,
ImportMode.ReminderRecord
};
}

View File

@ -60,6 +60,7 @@ if (!string.IsNullOrWhiteSpace(builder.Configuration["POSTGRES_CONNECTION"])){
builder.Services.AddSingleton<IExtraFieldDataAccess, PGExtraFieldDataAccess>();
builder.Services.AddSingleton<IInspectionRecordDataAccess, PGInspectionRecordDataAccess>();
builder.Services.AddSingleton<IInspectionRecordTemplateDataAccess, PGInspectionRecordTemplateDataAccess>();
builder.Services.AddSingleton<IEquipmentRecordDataAccess, PGEquipmentRecordDataAccess>();
builder.Services.AddSingleton<IUserHouseholdDataAccess, PGUserHouseholdDataAccess>();
}
else
@ -83,12 +84,14 @@ else
builder.Services.AddSingleton<IExtraFieldDataAccess, ExtraFieldDataAccess>();
builder.Services.AddSingleton<IInspectionRecordDataAccess, InspectionRecordDataAccess>();
builder.Services.AddSingleton<IInspectionRecordTemplateDataAccess, InspectionRecordTemplateDataAccess>();
builder.Services.AddSingleton<IEquipmentRecordDataAccess, EquipmentRecordDataAccess>();
builder.Services.AddSingleton<IUserHouseholdDataAccess, UserHouseholdDataAccess>();
}
//configure helpers
builder.Services.AddSingleton<IFileHelper, FileHelper>();
builder.Services.AddSingleton<IGasHelper, GasHelper>();
builder.Services.AddSingleton<IEquipmentHelper, EquipmentHelper>();
builder.Services.AddSingleton<IReminderHelper, ReminderHelper>();
builder.Services.AddSingleton<IReportHelper, ReportHelper>();
builder.Services.AddSingleton<IConfigHelper, ConfigHelper>();

View File

@ -214,6 +214,7 @@
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 />
equipmentRecordId - equipment ids separated by space(optional) <br/>
}
</div>
</div>
@ -237,6 +238,7 @@
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 />
equipmentRecordId - equipment ids separated by space(optional) <br />
}
</div>
</div>

View File

@ -28,6 +28,7 @@
<!option @(Model.Id == (int)ImportMode.PlanRecord ? "selected" : "") value="@((int)ImportMode.PlanRecord)">@translator.Translate(userLanguage, "Planner")</!option>
<!option @(Model.Id == (int)ImportMode.OdometerRecord ? "selected" : "") value="@((int)ImportMode.OdometerRecord)">@translator.Translate(userLanguage, "Odometer")</!option>
<!option @(Model.Id == (int)ImportMode.NoteRecord ? "selected" : "") value="@((int)ImportMode.NoteRecord)">@translator.Translate(userLanguage, "Notes")</!option>
<!option @(Model.Id == (int)ImportMode.EquipmentRecord ? "selected" : "") value="@((int)ImportMode.EquipmentRecord)">@translator.Translate(userLanguage, "Equipment")</!option>
<!option @(Model.Id == (int)ImportMode.VehicleRecord ? "selected" : "") value="@((int)ImportMode.VehicleRecord)">@translator.Translate(userLanguage, "Vehicle")</!option>
</select>
</div>

View File

@ -149,6 +149,9 @@
case ImportMode.InspectionRecord:
<li><a class="context-menu-active-single dropdown-item @StaticHelper.DefaultActiveTab(userConfig, ImportMode.InspectionRecord)" href="#" onclick="viewVehicleWithTab(selectedVehicles, 'inspection')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Inspections")</span><i class="bi bi-clipboard-check"></i></div></a></li>
break;
case ImportMode.EquipmentRecord:
<li><a class="context-menu-active-single dropdown-item @StaticHelper.DefaultActiveTab(userConfig, ImportMode.EquipmentRecord)" href="#" onclick="viewVehicleWithTab(selectedVehicles, 'equipment')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Equipment")</span><i class="bi bi-disc"></i></div></a></li>
break;
case ImportMode.ReminderRecord:
<li><a class="context-menu-active-single dropdown-item @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" href="#" onclick="viewVehicleWithTab(selectedVehicles, 'reminder')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Reminders")</span><i class="bi bi-bell"></i></div></a></li>
break;

View File

@ -131,14 +131,14 @@
</div>
<div class="col-12 col-md-6">
<ul class="list-group">
<li class="list-group-item">
<input onChange="updateSettings()" disabled class="form-check-input me-1" type="checkbox" value="Dashboard" id="dashboardTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.Dashboard) ? "checked" : "")>
<label class="form-check-label stretched-link" for="dashboardTab">@translator.Translate(userLanguage, "Dashboard")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="ServiceRecord" id="serviceRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.ServiceRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="serviceRecordTab">@translator.Translate(userLanguage, "Service Records")</label>
</li>
<li class="list-group-item d-none">
<input onChange="updateSettings()" disabled class="form-check-input me-1" type="checkbox" value="Dashboard" id="dashboardTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.Dashboard) ? "checked" : "")>
<label class="form-check-label stretched-link" for="dashboardTab">@translator.Translate(userLanguage, "Dashboard")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="RepairRecord" id="repairRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.RepairRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="repairRecordTab">@translator.Translate(userLanguage, "Repairs")</label>
@ -155,6 +155,10 @@
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="OdometerRecord" id="odometerRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.OdometerRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="odometerRecordTab">@translator.Translate(userLanguage, "Odometer")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="EquipmentRecord" id="equipmentRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.EquipmentRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="equipmentRecordTab">@translator.Translate(userLanguage, "Equipment")</label>
</li>
</ul>
</div>
<div class="col-12 col-md-6">
@ -202,6 +206,7 @@
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.PlanRecord)) value="PlanRecord">@translator.Translate(userLanguage, "Planner")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.OdometerRecord)) value="OdometerRecord">@translator.Translate(userLanguage, "Odometer")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.InspectionRecord)) value="InspectionRecord">@translator.Translate(userLanguage, "Inspections")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.EquipmentRecord)) value="EquipmentRecord">@translator.Translate(userLanguage, "Equipment")</!option>
</select>
</div>
<div class="col-12 col-md-6">
@ -384,6 +389,7 @@
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.TaxRecord)" draggable="true" data-tab="@ImportMode.TaxRecord">@translator.Translate(userLanguage, "Taxes")</li>
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.NoteRecord)" draggable="true" data-tab="@ImportMode.NoteRecord">@translator.Translate(userLanguage, "Notes")</li>
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x => x == ImportMode.InspectionRecord)" draggable="true" data-tab="@ImportMode.InspectionRecord">@translator.Translate(userLanguage, "Inspections")</li>
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x => x == ImportMode.EquipmentRecord)" draggable="true" data-tab="@ImportMode.EquipmentRecord">@translator.Translate(userLanguage, "Equipment")</li>
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.ReminderRecord)" draggable="true" data-tab="@ImportMode.ReminderRecord">@translator.Translate(userLanguage, "Reminder")</li>
</ul>
</div>

View File

@ -0,0 +1,74 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model EquipmentRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Equipment Record") : translator.Translate(userLanguage, "Edit Equipment Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditEquipmentRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddEquipmentRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="equipmentRecordDescription">@translator.Translate(userLanguage, "Description")</label>
<input type="text" id="equipmentRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of equipment")" value="@Model.Description">
<label for="equipmentRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="equipmentRecordTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="equipmentRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="equipmentRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
<div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" type="checkbox" role="switch" id="equipmentEquippedCheck" checked="@Model.IsEquipped">
<label class="form-check-label" for="equipmentEquippedCheck">@translator.Translate(userLanguage, "Is Equipped")</label>
</div>
</div>
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
@if (Model.OdometerRecords.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleOdometerHistory()"><i class="bi bi-speedometer"></i></button>
}
<button type="button" class="btn btn-danger" onclick="deleteEquipmentRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage, "Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddEquipmentRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveEquipmentRecordToVehicle()">@translator.Translate(userLanguage, "Add New Equipment Record")</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveEquipmentRecordToVehicle(true)">@translator.Translate(userLanguage, "Edit Equipment Record")</button>
}
</div>
@await Html.PartialAsync("Odometer/_OdometerHistory", Model.OdometerRecords)
<script>
function getEquipmentRecordModelData() {
return { id: @Model.Id}
}
</script>

View File

@ -0,0 +1,188 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var enableCsvImports = userConfig.EnableCsvImports;
var hideZero = userConfig.HideZero;
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var userLanguage = userConfig.UserLanguage;
var extraFields = new List<string>();
if (userConfig.EnableExtraFieldColumns)
{
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
}
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.EquipmentRecord);
}
@model List<EquipmentRecordViewModel>
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Equipment Records")}: {Model.Count()}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterTable('equipment-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddEquipmentRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Equipment Record")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('EquipmentRecord')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('EquipmentRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('equipment-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('EquipmentRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'EquipmentRecord')" type="checkbox" id="chkCol_Description" checked>
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('EquipmentRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='equipped' onChange="showTableColumns(this, 'EquipmentRecord')" type="checkbox" id="chkCol_Equipped" checked>
<label class="form-check-label stretched-link" for="chkCol_Equipped">@translator.Translate(userLanguage, "Equipped")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('EquipmentRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='distance' onChange="showTableColumns(this, 'EquipmentRecord')" type="checkbox" id="chkCol_Distance" checked>
<label class="form-check-label stretched-link" for="chkCol_Distance">@translator.Translate(userLanguage, "Distance")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('EquipmentRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'EquipmentRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('EquipmentRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'EquipmentRecord')" type="checkbox" id="chkCol_Notes" checked>
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div>
</li>
@foreach (string extraFieldColumn in extraFields)
{
var elementId = Guid.NewGuid();
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('EquipmentRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'EquipmentRecord')" type="checkbox" id="@elementId">
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div>
</li>
}
</ul>
</div>
}
else
{
<button onclick="showAddEquipmentRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Equipment Record")</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="equipped" style="cursor:pointer;">@translator.Translate(userLanguage, "Equipped")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="distance" onclick="toggleSort('equipment-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Distance")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
<tbody>
@foreach (EquipmentRecordViewModel equipmentRecord in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@equipmentRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditEquipmentRecordModal,@equipmentRecord.Id)" data-tags='@string.Join(" ", equipmentRecord.Tags)'>
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description" data-record-type="cost">@equipmentRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="equipped"><i class="bi bi-check-lg @(equipmentRecord.IsEquipped ? "" : "d-none")"></i></td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="distance">@(equipmentRecord.DistanceTraveled == default ? "---" : equipmentRecord.DistanceTraveled)</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", equipmentRecord.Files)</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(equipmentRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = equipmentRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="equipmentRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="equipmentRecordModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'EquipmentRecord')"><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, 'EquipmentRecord')"><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><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'EquipmentRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'EquipmentRecord')"><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>
</ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@ -0,0 +1,23 @@
@inject IConfigHelper config
@inject ITranslationHelper translator
@using CarCareTracker.Helper
@model List<EquipmentRecord>
@{
var userLanguage = config.GetUserConfig(User).UserLanguage;
}
@if (Model.Any())
{
<div id="equipmentSelector">
<ul class="list-group">
@foreach (EquipmentRecord equipmentRecord in Model)
{
<li class="list-group-item text-start">
<input class="form-check-input" type="checkbox" value="@equipmentRecord.Id" id="equipmentCheck_@equipmentRecord.Id" checked="@equipmentRecord.IsEquipped">
<label class="form-check-label stretched-link" for="equipmentCheck_@equipmentRecord.Id">@equipmentRecord.Description</label>
</li>
}
</ul>
</div>
}

View File

@ -23,6 +23,7 @@
<script src="~/js/planrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/odometerrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/inspectionrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/equipmentrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/lib/chart-js/chart.umd.js"></script>
<script src="~/lib/drawdown/drawdown.js"></script>
}
@ -71,6 +72,9 @@
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x => x == ImportMode.InspectionRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.InspectionRecord)" id="inspection-tab" data-bs-toggle="tab" data-bs-target="#inspection-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-clipboard-check"></i><span class="ms-2">@translator.Translate(userLanguage, "Inspections")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x => x == ImportMode.EquipmentRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.EquipmentRecord)" id="equipment-tab" data-bs-toggle="tab" data-bs-target="#equipment-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-disc"></i><span class="ms-2">@translator.Translate(userLanguage, "Equipment")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ReminderRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell"></i></div><span class="ms-2">@translator.Translate(userLanguage, "Reminders")</span></button>
</li>
@ -136,6 +140,9 @@
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x => x == ImportMode.InspectionRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.InspectionRecord)" id="inspection-tab" data-bs-toggle="tab" data-bs-target="#inspection-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-clipboard-check me-2"></i>@translator.Translate(userLanguage, "Inspections")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x => x == ImportMode.EquipmentRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.EquipmentRecord)" id="equipment-tab" data-bs-toggle="tab" data-bs-target="#equipment-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-disc me-2"></i>@translator.Translate(userLanguage, "Equipment")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ReminderRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>@translator.Translate(userLanguage, "Reminders")</span></button>
</li>
@ -155,6 +162,7 @@
<div class="tab-pane fade" id="plan-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade" id="odometer-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade" id="inspection-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade" id="equipment-tab-pane" role="tabpanel" tabindex="0"></div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="editVehicleModal" tabindex="-1" role="dialog" aria-hidden="true">

View File

@ -0,0 +1,53 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<OdometerRecord>
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div id="odometerHistoryModalContainer" class="d-none">
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage, "Odometer History")</h5>
</div>
<div class="modal-body">
@if (Model.Any())
{
<div class="row">
<div class="col-12" style="max-height:50vh; overflow-y:auto;">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Initial Odometer")</th>
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Distance")</th>
</tr>
</thead>
<tbody>
@foreach (OdometerRecord odometerHistory in Model)
{
<tr class="d-flex">
<td class="col-3">@StaticHelper.TruncateStrings(odometerHistory.Date.ToShortDateString())</td>
<td class="col-3">@odometerHistory.InitialMileage</td>
<td class="col-3">@odometerHistory.Mileage</td>
<td class="col-3">@(odometerHistory.DistanceTraveled == default ? "---" : odometerHistory.DistanceTraveled)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
else
{
<div class="row">
<div class="col-12">
<div class="text-center">
<h4>@translator.Translate(userLanguage, "No odometer history")</h4>
</div>
</div>
</div>
}
</div>
</div>

View File

@ -77,6 +77,11 @@
<div class="col-md-6 col-12">
<label for="odometerRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="odometerRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.EquipmentRecords.Any())
{
<label for="equipmentSelector">@translator.Translate(userLanguage, "Equipment")</label>
@await Html.PartialAsync("Equipment/_EquipmentSelector", Model.EquipmentRecords)
}
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())

View File

@ -31,6 +31,16 @@
<div class="col-md-6 col-12">
<label for="odometerRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="odometerRecordNotes" class="form-control" rows="5"></textarea>
<div class="@(Model.EquipmentRecords.Any() ? "" : "d-none")">
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" type="checkbox" role="switch" id="equipmentEditCheck" onchange="toggleEquipmentEdit()">
<label class="form-check-label" for="equipmentEditCheck">@translator.Translate(userLanguage, "Edit Equipment")</label>
</div>
</div>
<div id="equipmentEditContainer" class="d-none">
<label for="equipmentSelector">@translator.Translate(userLanguage, "Equipment")</label>
@await Html.PartialAsync("Equipment/_EquipmentSelector", Model.EquipmentRecords)
</div>
</div>
</div>
</div>

View File

@ -67,6 +67,8 @@
getVehiclePlanRecords(vehicleId);
} else if (mode == "OdometerRecord") {
getVehicleOdometerRecords(vehicleId);
} else if (mode == "EquipmentRecord"){
getVehicleEquipmentRecords(vehicleId);
}
} else {
errorToast(genericErrorMessage());

View File

@ -178,6 +178,101 @@
</div>
}
<script>setMarkDownStickerNotes()</script>
} else if (Model.EquipmentRecords.Any())
{
@foreach (EquipmentRecordStickerViewModel equipmentRecord in Model.EquipmentRecords)
{
<div class="d-flex flex-column recordSticker">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
</div>
<hr />
<div class="row">
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
<span class="display-6">@($"{Model.VehicleData.Year} {Model.VehicleData.Make} {Model.VehicleData.Model}")</span>
</li>
<li class="list-group-item">
<span class="lead">@($"{StaticHelper.GetVehicleIdentifier(Model.VehicleData)}")</span>
</li>
@foreach (ExtraField extraField in Model.VehicleData.ExtraFields)
{
if (!string.IsNullOrWhiteSpace(extraField.Value))
{
<li class="list-group-item">
<span class="lead">@($"{extraField.Name}: {extraField.Value}")</span>
</li>
}
}
</ul>
</div>
<div class="col-6">
<ul class="list-group">
@if (!string.IsNullOrWhiteSpace(equipmentRecord.Description))
{
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Description")}: {equipmentRecord.Description}")
</li>
}
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Distance")}: {equipmentRecord.Distance}")
</li>
@if (equipmentRecord.IsEquipped)
{
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Equipped")}:")<i class="bi ms-2 bi-check-lg"></i>
</li>
}
@foreach (ExtraField extraField in equipmentRecord.ExtraFields)
{
<li class="list-group-item">
@($"{extraField.Name}: {extraField.Value}")
</li>
}
</ul>
</div>
</div>
<hr />
@if (equipmentRecord.OdometerRecords.Any())
{
<div class="row">
<div class="col-12">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Initial Odometer")</th>
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Distance")</th>
</tr>
</thead>
<tbody>
@foreach (OdometerRecord odometerHistory in equipmentRecord.OdometerRecords)
{
<tr class="d-flex">
<td class="col-3">@StaticHelper.TruncateStrings(odometerHistory.Date.ToShortDateString())</td>
<td class="col-3">@odometerHistory.InitialMileage</td>
<td class="col-3">@odometerHistory.Mileage</td>
<td class="col-3">@(odometerHistory.DistanceTraveled == default ? "---" : odometerHistory.DistanceTraveled)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<hr />
}
<div class="row stickerNoteContainer">
<div class="col-12">
<div class="stickerNote ms-1 me-1 p-1">
@(equipmentRecord.Notes)
</div>
</div>
</div>
</div>
}
<script>setMarkDownStickerNotes()</script>
} else if (Model.SupplyRecords.Any()){
@foreach (SupplyRecord supplyRecord in Model.SupplyRecords)
{

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,124 @@
function showAddEquipmentRecordModal() {
$.get('/Vehicle/GetAddEquipmentRecordPartialView', function (data) {
if (data) {
$("#equipmentRecordModalContent").html(data);
initTagSelector($("#equipmentRecordTag"));
$('#equipmentRecordModal').modal('show');
}
});
}
function showEditEquipmentRecordModal(equipmentRecordId, nocache) {
if (!nocache) {
var existingContent = $("#equipmentRecordModalContent").html();
if (existingContent.trim() != '') {
//check if id is same.
var existingId = getEquipmentRecordModelData().id;
if (existingId == equipmentRecordId && $('[data-changed=true]').length > 0) {
$('#equipmentRecordModal').modal('show');
$('.cached-banner').show();
return;
}
}
}
$.get(`/Vehicle/GetEquipmentRecordForEditById?equipmentRecordId=${equipmentRecordId}`, function (data) {
if (data) {
$("#equipmentRecordModalContent").html(data);
initTagSelector($("#equipmentRecordTag"));
$('#equipmentRecordModal').modal('show');
bindModalInputChanges('equipmentRecordModal');
$('#equipmentRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("equipmentRecordNotes");
}
});
}
});
}
function hideAddEquipmentRecordModal() {
$('#equipmentRecordModal').modal('hide');
}
function deleteEquipmentRecord(equipmentRecordId) {
$("#workAroundInput").show();
Swal.fire({
title: "Confirm Deletion?",
text: "Deleted Equipment Records cannot be restored.",
showCancelButton: true,
confirmButtonText: "Delete",
confirmButtonColor: "#dc3545"
}).then((result) => {
if (result.isConfirmed) {
$.post(`/Vehicle/DeleteEquipmentRecordById?equipmentRecordId=${equipmentRecordId}`, function (data) {
if (data.success) {
hideAddEquipmentRecordModal();
successToast("Equipment Record Deleted");
var vehicleId = GetVehicleId().vehicleId;
getVehicleEquipmentRecords(vehicleId);
} else {
errorToast(data.message);
$("#workAroundInput").hide();
}
});
} else {
$("#workAroundInput").hide();
}
});
}
function saveEquipmentRecordToVehicle(isEdit) {
//get values
var formValues = getAndValidateEquipmentRecordValues();
//validate
if (formValues.hasError) {
errorToast("Please check the form data");
return;
}
//save to db.
$.post('/Vehicle/SaveEquipmentRecordToVehicleId', { equipmentRecord: formValues }, function (data) {
if (data.success) {
successToast(isEdit ? "Equipment Record Updated" : "Equipment Record Added.");
hideAddEquipmentRecordModal();
saveScrollPosition();
getVehicleEquipmentRecords(formValues.vehicleId);
} else {
errorToast(data.message);
}
})
}
function getAndValidateEquipmentRecordValues() {
var equipmentDescription = $("#equipmentRecordDescription").val();
var equipmentNotes = $("#equipmentRecordNotes").val();
var equipmentTags = $("#equipmentRecordTag").val();
var equipmentIsEquipped = $("#equipmentEquippedCheck").is(":checked");
var vehicleId = GetVehicleId().vehicleId;
var equipmentRecordId = getEquipmentRecordModelData().id;
//validation
var hasError = false;
var extraFields = getAndValidateExtraFields();
if (extraFields.hasError) {
hasError = true;
}
if (equipmentDescription.trim() == '') {
hasError = true;
$("#equipmentRecordDescription").addClass("is-invalid");
} else {
$("#equipmentRecordDescription").removeClass("is-invalid");
}
return {
id: equipmentRecordId,
hasError: hasError,
vehicleId: vehicleId,
description: equipmentDescription,
isEquipped: equipmentIsEquipped,
notes: equipmentNotes,
files: uploadedFiles,
tags: equipmentTags,
extraFields: extraFields.extraFields
}
}
function toggleOdometerHistory() {
let odometerHistoryContainer = $("#odometerHistoryModalContainer");
if (odometerHistoryContainer.hasClass('d-none')) {
odometerHistoryContainer.removeClass('d-none');
} else {
odometerHistoryContainer.addClass('d-none');
}
}

View File

@ -98,6 +98,7 @@ function getAndValidateOdometerRecordValues() {
var serviceTags = $("#odometerRecordTag").val();
var vehicleId = GetVehicleId().vehicleId;
var odometerRecordId = getOdometerRecordModelData().id;
var odometerEquipment = getSelectedEquipment();
//Odometer Adjustments
serviceMileage = GetAdjustedOdometer(odometerRecordId, serviceMileage);
//validation
@ -134,7 +135,8 @@ function getAndValidateOdometerRecordValues() {
notes: serviceNotes,
tags: serviceTags,
files: uploadedFiles,
extraFields: extraFields.extraFields
extraFields: extraFields.extraFields,
equipmentRecordId: odometerEquipment
}
}
@ -156,7 +158,8 @@ function editMultipleOdometerRecords(ids) {
if (ids.length < 2) {
return;
}
$.post('/Vehicle/GetOdometerRecordsEditModal', { recordIds: ids }, function (data) {
let vehicleId = GetVehicleId().vehicleId;
$.post('/Vehicle/GetOdometerRecordsEditModal', { recordIds: ids, vehicleId: vehicleId }, function (data) {
if (data) {
$("#odometerRecordModalContent").html(data);
//initiate datepicker
@ -175,6 +178,8 @@ function saveMultipleOdometerRecordsToVehicle() {
var odometerNotes = $("#odometerRecordNotes").val();
var odometerTags = $("#odometerRecordTag").val();
var odometerExtraFields = getAndValidateExtraFields();
let odometerEditEquipment = $('#equipmentEditCheck').is(':checked');
let odometerEquipment = getSelectedEquipment();
//validation
var hasError = false;
if (odometerMileage.trim() != '' && (isNaN(odometerMileageToParse) || parseInt(odometerMileageToParse) < 0)) {
@ -195,13 +200,15 @@ function saveMultipleOdometerRecordsToVehicle() {
}
var formValues = {
recordIds: recordsToEdit,
editEquipment: odometerEditEquipment,
editRecord: {
date: odometerDate,
initialMileage: initialOdometerMileageToParse,
mileage: odometerMileageToParse,
notes: odometerNotes,
tags: odometerTags,
extraFields: odometerExtraFields.extraFields
extraFields: odometerExtraFields.extraFields,
equipmentRecordId: odometerEquipment
}
}
$.post('/Vehicle/SaveMultipleOdometerRecords', { editModel: formValues }, function (data) {
@ -434,4 +441,18 @@ function duplicateDistanceToOtherVehicles(ids) {
errorToast(genericErrorMessage());
}
})
}
function getSelectedEquipment() {
var selectedEquipmentArray = [];
$("#equipmentSelector :checked").map(function () {
selectedEquipmentArray.push(this.value);
});
return selectedEquipmentArray;
}
function toggleEquipmentEdit() {
if ($('#equipmentEditCheck').is(":checked")) {
$('#equipmentEditContainer').removeClass('d-none');
} else {
$('#equipmentEditContainer').addClass('d-none');
}
}

View File

@ -860,7 +860,8 @@ function getSavedCSVExportParameters(mode) {
}
function exportVehicleData(mode) {
var vehicleId = GetVehicleId().vehicleId;
if (mode != 'PlanRecord') {
let bypassRecordTypes = ['PlanRecord', 'EquipmentRecord'];
if (!bypassRecordTypes.includes(mode)) {
$.get('/Vehicle/GetCSVExportParameters', function (paramData) {
if (paramData) {
Swal.fire({
@ -1056,6 +1057,11 @@ function deleteRecords(ids, source) {
case "InspectionRecord":
friendlySource = "Inspection Records";
refreshDataCallBack = getVehicleInspectionRecords;
break;
case "EquipmentRecord":
friendlySource = "Equipment Records";
refreshDataCallBack = getVehicleEquipmentRecords;
break;
}
Swal.fire({
@ -1133,6 +1139,11 @@ function duplicateRecords(ids, source) {
case "InspectionRecord":
friendlySource = "Inspection Record";
refreshDataCallBack = hideInspectionRecordTemplateModal;
break;
case "EquipmentRecord":
friendlySource = "Equipment Records";
refreshDataCallBack = getVehicleEquipmentRecords;
break;
}
Swal.fire({
@ -1210,6 +1221,11 @@ function duplicateRecordsToOtherVehicles(ids, source) {
case "InspectionRecord":
friendlySource = "Inspection Record";
refreshDataCallBack = hideInspectionRecordTemplateModal;
break;
case "EquipmentRecord":
friendlySource = "Equipment Records";
refreshDataCallBack = getVehicleEquipmentRecords;
break;
}
$.get(`/Home/GetVehicleSelector?vehicleId=${GetVehicleId().vehicleId}`, function (data) {

View File

@ -39,6 +39,9 @@
case "inspection-tab":
getVehicleInspectionRecords(vehicleId);
break;
case "equipment-tab":
getVehicleEquipmentRecords(vehicleId);
break;
}
$(`.lubelogger-tab #${e.target.id}`).addClass('active');
$(`.lubelogger-mobile-nav #${e.target.id}`).addClass('active');
@ -80,6 +83,9 @@
case "inspection-tab":
$("#inspection-tab-pane").html("");
break;
case "equipment-tab":
$("#equipment-tab-pane").html("");
break;
}
$(`.lubelogger-tab #${e.relatedTarget.id}`).removeClass('active');
$(`.lubelogger-mobile-nav #${e.relatedTarget.id}`).removeClass('active');
@ -187,6 +193,15 @@ function getVehicleInspectionRecords(vehicleId) {
}
});
}
function getVehicleEquipmentRecords(vehicleId) {
$.get(`/Vehicle/GetEquipmentRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
if (data) {
$("#equipment-tab-pane").html(data);
restoreScrollPosition();
getVehicleHaveImportantReminders(vehicleId);
}
});
}
function getVehicleReport(vehicleId) {
$.get(`/Vehicle/GetReportPartialView?vehicleId=${vehicleId}`, function (data) {
if (data) {
@ -800,6 +815,15 @@ function loadGlobalSearchResult(recordId, recordType) {
}
$('#inspection-tab').tab('show');
waitForElement("#inspectionRecordModalContent", showEditInspectionRecordModal, recordId);
break;
case "EquipmentRecord":
if ($('#equipment-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#equipment-tab').tab('show');
waitForElement("#equipmentRecordModalContent", showEditEquipmentRecordModal, recordId);
break;
}
} else {
errorToast(data.message);
@ -851,5 +875,8 @@ function getDefaultTabName() {
case "InspectionRecord":
return 'inspection';
break;
case "EquipmentRecord":
return 'equipment';
break;
}
}